ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

深入解析 editor 项目 Core/Viewer 双轨系统架构:业务逻辑、渲染副作用与可注入的帧循环设计

深入解析 editor 项目 Core/Viewer 双轨系统架构:业务逻辑、渲染副作用与可注入的帧循环设计 深入解析 editor 项目 Core/Viewer 双轨系统架构业务逻辑、渲染副作用与可注入的帧循环设计【免费下载链接】editorOpen-source 3D architectural editor with a local CLI, MCP tools, and practical workflows for humans and AI agents.项目地址: https://gitcode.com/GitHub_Trending/editor93/editor本指南以wiki/architecture/systems.md为骨架系统讲解该项目中两类系统的职责边界、帧循环模式、安装挂载规则与新增流程并结合packages/core/src/systems/与packages/viewer/src/systems/的真实源码与挂载代码Viewer 挂载顺序展开验证。读完你可以掌握Core/Viewer 系统如何分工、def.geometry如何取代每个 kind 一个系统、帧优先级如何编排、以及如何在编辑器中注入自有系统而不改动 viewer 包。什么是 Systems系统在场景架构中的位置Systems 是场景架构中负责业务逻辑、几何生成与约束计算的运行时组件。它们运行在 Three.js 帧循环frame loop中从不被直接渲染组件自身return null。适用目录packages/core/src/systems/**与packages/viewer/src/systems/**挂载位置Viewer组件内部、与 renderers 并列见 packages/viewer/src/components/viewer/index.tsx一个关键的设计倾向在文档开头就被强调对于 registry 驱动的 node kind优先不要为每个 kind 单独写系统。如果某个 kind 的唯一职责是变脏后重建几何只需在定义上声明def.geometry框架自带的GeometrySystem会处理整个重建循环per-kind 系统只保留给额外职责——动画、跨 kind 的脏标记级联dirty cascade、按命名网格named mesh修改材质等。这部分与 node-definitions.md 的三字段模型直接呼应。两类系统Core 与 Viewer 的分工边界文档把系统划分为两类这是整个架构的第一条分界线。Core Systems ——packages/core/src/systems/纯逻辑无渲染、无 Three.js 对象。它们从useScene读取节点计算派生值几何、约束再写回场景状态。系统职责WallSystem墙体斜接mitering、转角连接CeilingSystem基于多边形polygon的天花板生成RoofSystem坡屋顶pitched roof造型DoorSystem墙上门洞的放置约束WindowSystem墙上窗洞的放置约束ItemSystem物品变换transforms、碰撞仓库中packages/core/src/systems/实际包含elevator/、fence/、slab/、stair/、wall/五个子目录对应更细粒度的纯逻辑模块例如wall/wall-mitering.ts墙角斜接计算、wall/wall-topology.ts、stair/stair-rise.ts踏步高度约束、slab/slab-placement.ts、fence/fence-spline.ts均带有同名的.test.ts测试文件印证了纯逻辑、可单测的设计目标。Slab 没有专属系统。文档特别指出Slab 几何通过 registry 的def.geometry渲染——packages/nodes/src/slab/definition.ts中geometry: buildSlabGeometrydefinition.ts调用packages/viewer/src/systems/slab/slab-system.tsx中的纯生成器并配一个极小的def.system只做脏标记跟踪。定义文件注释也确认Stage B:def.geometrydrives the rebuild via genericGeometrySystem;ParametricNodeRenderermounts the empty group. No per-kind renderer or system file.slab/definition.ts。Viewer Systems ——packages/viewer/src/systems/访问 Three.js 对象通过useRegistry管理渲染副作用。系统职责LevelSystem楼层堆叠 / 爆炸exploded/ 单独solo/ 手动定位WallCutout在墙体几何上开出门窗洞ZoneSystem区域显示与标签放置InteractiveSystem场景内物品的开关与滑块交互GuideSystem临时辅助几何ScanSystem点云渲染从仓库目录看viewer 的系统比文档表格更丰富ceiling/、column/、door/、elevator/、fence/、floor-elevation/、geometry/、guide/、interactive/、item/、item-light/、level/、perf-action-settle/、roof/、scan/、slab/、stair/、wall/、window/、zone/共 20 个子目录其中geometry/geometry-system.tsx就是上文提到的通用重建系统wall/下还有wall-cutout.tsx开洞、level-miter-cache.ts楼层级斜接缓存、wall-materials.ts等渲染侧模块。一个容易混淆的点CeilingSystem 的归属文档提到Ceiling geometry consumes dirty marks at frame priority 2, likeGeometrySystem。值得注意的是仓库中packages/viewer/src/systems/ceiling/ceiling-system.tsx也以帧优先级 2 消费脏标记且packages/editor/src/components/systems/ceiling/ceiling-system.tsx存在编辑器版。这说明同名系统可能同时在 viewer 与编辑器层各有一份按消费方注入——这正是Systems 是定制点的体现。帧优先级编排节点批处理、几何重建与脏标记的生命周期文档给出了一条重要的时间线源码完全印证节点批处理node batch在优先级 1 快照脏标记、在优先级 5 处理成员变化因此它能在重建后释放旧几何、收集替代品。GeometrySystemslab 等与天花板脏标记消费都运行在帧优先级 2。一个定义的system.priority只决定组件挂载顺序不设置useFrame优先级。源码证据node-batch/system.tsxuseFrame(captureChangedNodes, 1)与useFrame(() runBatchFrame(invalidate, wakeRef), 5)—— 优先级 1 捕获本帧触碰的节点优先级 5 执行批次帧逻辑。geometry-system.tsxuseFrame(() {...}, 2)优先级 2。wall-system.tsxuseFrame(() {...}, 4)墙体重建在优先级 4。level-system.tsxuseFrame((_, delta) {...}, 5)注释明确使用较低优先级让它在其他系统的变换稳定之后再运行。wall-cutout.tsxWALL_CUTOUT_FRAME_PRIORITY 0相机裁剪标记最先写入。GeometrySystem的重建循环geometry-system.tsx值得展开Phase 1按(kind, parentId)对脏节点分组Phase 2对声明了def.computeLevelData的 kind按组做一次批量预计算墙体斜接是典型场景避免同帧多个同 kind 兄弟节点 O(N²) 重算Phase 3逐节点调用def.geometry纯构建器disposeChildren只清理带userData.__fromGeometry标记的旧子节点然后挂载新子节点、clearDirty(id)若声明了def.geometryKey输入未变化时直接复用上次构建结果跳过 disposerebuild例如物品改挂到置物架上时置物架变脏但其板材未变。系统的幂等要求给定相同的节点产生相同的输出不要在无脏标记时每帧跑昂贵逻辑。脏标记通过useScene的markDirty写入dirtyNodes由clearDirty消费。模式返回 null 的 React 组件 useFrame系统的统一模式是React 组件渲染null用useFrame做逐帧逻辑。文档给出最小骨架// packages/core/src/systems/my-system.tsx import { useFrame } from react-three/fiber import { useScene } from ../store/use-scene export function MySystem() { const nodes useScene(s s.nodes) useFrame(() { // compute and write back derived state }) return null }Core 与 Viewer 系统都挂载在Viewer内部、与 renderers 并列。从 index.tsx 可以看到实际挂载顺序ErrorBoundary 内Lights /→SceneBvh包裹SceneRenderer /渲染器FloorElevationSystem /—— 处理capabilities.floorPlaced的楼层抬升GeometrySystem /—— 通用几何重建循环StairOpeningSystem /—— 楼梯开口同步RegisteredSystems /—— 挂载所有 registry 贡献的系统按system.priority排序PostProcessing /、SelectionManager /、PerfMonitor /、PerfActionSettleSystem /、BatchedMeshSpike /{children}——消费方注入的自定义系统其中RegisteredSystemsregistered-systems.tsx的实现细节值得注意从nodeRegistry.entries()过滤出声明了def.system的 kind按system.priority升序排序默认DEFAULT_PRIORITY 5渲染每个系统用独立的Suspense边界包裹一个 chunk 加载失败不能拖垮整个墙体管线依赖useRegistryVersion()重派生列表避免异步插件注册后被永久遗漏SystemContribution类型定义在 packages/core/src/registry/types.ts{ module: () Promise{ default: ComponentType{ sceneApi: SceneApi } }, priority?: number }。Systems 是定制点任何Viewer的消费方——编辑器应用、嵌入页、只读预览——都可以把自己的系统作为 children 注入。这正是编辑器侧行为空间检测、工具反馈在不触碰 viewer 包的前提下被添加的方式。实际代码中编辑器在 packages/editor/src/components/editor/index.tsx 内挂载了ZoneSystem、CeilingSystem、InteractiveSystem、ToolManager等大量自定义系统。规则清单文档明确了五条不可违背的规则Core 系统不得导入 Three.js—— 只处理纯数据。Viewer 系统不得包含业务逻辑—— 域级规则委托给 Core。系统与渲染器之间绝不重复逻辑—— 渲染器需要什么就由系统计算并存储渲染器只读结果。系统应当幂等—— 相同输入产生相同输出。用dirty标记驱动重跑避免无脏检查地每帧执行昂贵逻辑。卸载时清理模块级缓存—— 跨帧存活的缓存也会跨挂载存活按 level 或 node ID 作 key 的缓存会随标签页内打开的每个项目无限增长。必须在系统的 unmount effect 中重置正如编辑器 teardown 调用spatialGridManager.clear()见 packages/editor/src/components/editor/index.tsx。源码中的具体落实WallSystem在 unmount effect 中调用clearLevelMiterCache()wall-system.tsx注释明确miter 缓存是模块级的会存活于本次挂载之外编辑器 teardown 会重置其他共享单例这里必须同样重置否则同标签页内重新挂载时上一楼层所有墙体仍然可达。节点批处理系统同样提供resetNodeBatchState()node-batch/system.tsx清空changedNodes、staleNodes、partialNodes、leftoverNodes等模块级集合。Reconciliation 与场景提交Scene Commits写入持久化场景数据的调和reconciliation必须把每一次派生写入放进可传输的场景提交中。文档以空间检测为例一次墙体编辑可能同时创建 slab 和 ceiling、更新墙侧分类、扩充level.children。这些写入属于发起编辑的一部分必须出现在该编辑的SceneCommit.current快照中并保持为一个 undo 步骤。存储订阅顺序满足该契约reconciliation 在 history 中间件捕获 commit 之前完成。若把 reconciliation 移到subscribeSceneCommits则会破坏契约除非它单独发射一个可传输的 commit因为 commit 监听器在快照已捕获之后才运行且 history 暂停期间的写入只会存在于本地 live store。远端操作应用发起 commit 携带的生成节点接收端客户端不得独立重新生成它们变更锁与只读守卫见 scene-api-security.ts 与 empty-graph-guard.ts 等配套模块防止客户端为同一派生表面铸造不同 ID。任何把 reconciliation 限定到节点/房间子集的优化都必须与全楼层扫描做等价性测试代表性的 create、update、delete、cascade、split、merge、corridor-enclosure 编辑必须产生与全量 reconciliation 相同的空间与表面。新增一个系统三步决策 挂载顺序文档给出了标准流程这里结合源码补充细节1. 决定作用域域逻辑→packages/core/src/systems/纯逻辑、可单测参照wall/wall-mitering.ts、stair/stair-rise.tsViewer 渲染副作用→packages/viewer/src/systems/挂载进 packages/viewer/src/components/viewer/index.tsx编辑器专属或集成专属→ 放在消费应用中如packages/editor/src/components/systems/作为Viewer的 children 注入2. 创建name-system.tsx放在对应目录。3. 在正确位置挂载viewer 内部系统放进packages/viewer/src/components/viewer/index.tsx参考GeometrySystem、FloorElevationSystem、StairOpeningSystem、RegisteredSystems的排布应用专属系统从外部注入// apps/editor —— 编辑器注入自己的系统无需修改 viewer Viewer MyEditorSystem / ToolManager / /Viewer4. 挂载顺序至关重要。大多数 viewer 系统在 JSX 树中位于渲染器之后——它们消费渲染器挂载时填充的sceneRegistry数据。只有当系统明确不读取 registry 时才可以放在渲染器之前。文档还补充了一条经验Reconciliation that writes persisted scene data must keep every derived write in a transmittable scene commit——新系统若参与写持久化数据必须遵守上一节的提交契约。与 node-definitions.md 的衔接systems.md与 node-definitions.md 是同一架构的两面node-definitions 定义三字段模型geometry纯构建器、renderer自定义 React 组件、system逐帧组件。三者独立、以存在性参与。当一个 kind 的几何只是脏标记的纯函数时不要写 per-kind 系统——声明def.geometry即可GeometrySystem会处理重建shelf、spawn、item、column、fence segment、slab 都是如此。当一个 kind 同时有参数化几何和额外职责时door、window才采用geometrysystem组合geometry纯函数构建可见网格system推进动画如operationState动画完成时标记一次 dirty 触发重建。文档特别提醒不要在每次动画 tick 时markDirty——脏标记是一次性工作、必须排空到零每 tick 标记会让场景永远无法 settle破坏?perf安定检测器与按需渲染的静默门控。小结Systems 层是 editor 项目场景架构的中枢Core 系统用纯数据函数承载全部业务逻辑与约束Viewer 系统在帧循环中把派生结果转成渲染副作用而Viewer的可注入 children 机制让编辑器、嵌入页与预览各自扩展行为而不触碰 viewer 包。理解def.geometry优先原则、帧优先级编排批处理 1 → 几何重建 2 → 墙体 4 → 楼层 5、以及系统计算、渲染器读取的不重复原则是向该架构贡献新系统的前提。【免费下载链接】editorOpen-source 3D architectural editor with a local CLI, MCP tools, and practical workflows for humans and AI agents.项目地址: https://gitcode.com/GitHub_Trending/editor93/editor创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表