@363045841yyt/klinechart-core 0.9.0-alpha.0 → 0.9.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllers/createChartController.d.ts.map +1 -1
- package/dist/controllers/createChartController.js +31 -13
- package/dist/controllers/createChartController.js.map +1 -1
- package/dist/controllers/index.d.ts +1 -0
- package/dist/controllers/index.d.ts.map +1 -1
- package/dist/controllers/index.js.map +1 -1
- package/dist/controllers/types.d.ts +12 -0
- package/dist/controllers/types.d.ts.map +1 -1
- package/dist/engine/chart.d.ts +29 -11
- package/dist/engine/chart.d.ts.map +1 -1
- package/dist/engine/chart.js +147 -25
- package/dist/engine/chart.js.map +1 -1
- package/dist/engine/controller/interaction.d.ts +29 -4
- package/dist/engine/controller/interaction.d.ts.map +1 -1
- package/dist/engine/controller/interaction.js +78 -31
- package/dist/engine/controller/interaction.js.map +1 -1
- package/dist/engine/data/incrementalLoadHint.d.ts.map +1 -1
- package/dist/engine/data/incrementalLoadHint.js +5 -1
- package/dist/engine/data/incrementalLoadHint.js.map +1 -1
- package/dist/engine/drawing/DrawingState.d.ts +19 -34
- package/dist/engine/drawing/DrawingState.d.ts.map +1 -1
- package/dist/engine/drawing/DrawingState.js +110 -92
- package/dist/engine/drawing/DrawingState.js.map +1 -1
- package/dist/engine/drawing/index.d.ts +4 -1
- package/dist/engine/drawing/index.d.ts.map +1 -1
- package/dist/engine/drawing/index.js +9 -4
- package/dist/engine/drawing/index.js.map +1 -1
- package/dist/engine/drawing/interaction.d.ts +9 -29
- package/dist/engine/drawing/interaction.d.ts.map +1 -1
- package/dist/engine/drawing/interaction.js +23 -40
- package/dist/engine/drawing/interaction.js.map +1 -1
- package/dist/engine/indicators/chartIndicatorManager.d.ts.map +1 -1
- package/dist/engine/indicators/chartIndicatorManager.js +4 -12
- package/dist/engine/indicators/chartIndicatorManager.js.map +1 -1
- package/dist/engine/layout/chartPaneLayout.d.ts +0 -2
- package/dist/engine/layout/chartPaneLayout.d.ts.map +1 -1
- package/dist/engine/layout/chartPaneLayout.js +5 -9
- package/dist/engine/layout/chartPaneLayout.js.map +1 -1
- package/dist/engine/layout/pane.d.ts +0 -39
- package/dist/engine/layout/pane.d.ts.map +1 -1
- package/dist/engine/layout/pane.js +0 -9
- package/dist/engine/layout/pane.js.map +1 -1
- package/dist/engine/paneRenderer.d.ts +1 -10
- package/dist/engine/paneRenderer.d.ts.map +1 -1
- package/dist/engine/paneRenderer.js +2 -19
- package/dist/engine/paneRenderer.js.map +1 -1
- package/dist/engine/render/chartRenderer.d.ts +49 -8
- package/dist/engine/render/chartRenderer.d.ts.map +1 -1
- package/dist/engine/render/chartRenderer.js +143 -72
- package/dist/engine/render/chartRenderer.js.map +1 -1
- package/dist/engine/renderers/Indicator/atr.js +3 -13
- package/dist/engine/renderers/Indicator/atr.js.map +1 -1
- package/dist/engine/renderers/Indicator/boll.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/boll.js +12 -24
- package/dist/engine/renderers/Indicator/boll.js.map +1 -1
- package/dist/engine/renderers/Indicator/cci.js +6 -13
- package/dist/engine/renderers/Indicator/cci.js.map +1 -1
- package/dist/engine/renderers/Indicator/chaikinVol.js +3 -11
- package/dist/engine/renderers/Indicator/chaikinVol.js.map +1 -1
- package/dist/engine/renderers/Indicator/cmf.js +3 -11
- package/dist/engine/renderers/Indicator/cmf.js.map +1 -1
- package/dist/engine/renderers/Indicator/dema.js +3 -11
- package/dist/engine/renderers/Indicator/dema.js.map +1 -1
- package/dist/engine/renderers/Indicator/donchian.js +3 -11
- package/dist/engine/renderers/Indicator/donchian.js.map +1 -1
- package/dist/engine/renderers/Indicator/ene.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/ene.js +12 -24
- package/dist/engine/renderers/Indicator/ene.js.map +1 -1
- package/dist/engine/renderers/Indicator/expma.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/expma.js +5 -11
- package/dist/engine/renderers/Indicator/expma.js.map +1 -1
- package/dist/engine/renderers/Indicator/fastk.js +6 -13
- package/dist/engine/renderers/Indicator/fastk.js.map +1 -1
- package/dist/engine/renderers/Indicator/fib.js +3 -11
- package/dist/engine/renderers/Indicator/fib.js.map +1 -1
- package/dist/engine/renderers/Indicator/hma.js +3 -11
- package/dist/engine/renderers/Indicator/hma.js.map +1 -1
- package/dist/engine/renderers/Indicator/hv.js +3 -11
- package/dist/engine/renderers/Indicator/hv.js.map +1 -1
- package/dist/engine/renderers/Indicator/ichimoku.js +24 -18
- package/dist/engine/renderers/Indicator/ichimoku.js.map +1 -1
- package/dist/engine/renderers/Indicator/kama.js +3 -11
- package/dist/engine/renderers/Indicator/kama.js.map +1 -1
- package/dist/engine/renderers/Indicator/keltner.js +3 -11
- package/dist/engine/renderers/Indicator/keltner.js.map +1 -1
- package/dist/engine/renderers/Indicator/kst.js +9 -17
- package/dist/engine/renderers/Indicator/kst.js.map +1 -1
- package/dist/engine/renderers/Indicator/ma.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/ma.js +10 -21
- package/dist/engine/renderers/Indicator/ma.js.map +1 -1
- package/dist/engine/renderers/Indicator/macd.js +14 -44
- package/dist/engine/renderers/Indicator/macd.js.map +1 -1
- package/dist/engine/renderers/Indicator/mfi.js +3 -11
- package/dist/engine/renderers/Indicator/mfi.js.map +1 -1
- package/dist/engine/renderers/Indicator/mom.js +6 -13
- package/dist/engine/renderers/Indicator/mom.js.map +1 -1
- package/dist/engine/renderers/Indicator/obv.js +3 -11
- package/dist/engine/renderers/Indicator/obv.js.map +1 -1
- package/dist/engine/renderers/Indicator/parkinson.js +3 -11
- package/dist/engine/renderers/Indicator/parkinson.js.map +1 -1
- package/dist/engine/renderers/Indicator/pvt.js +3 -11
- package/dist/engine/renderers/Indicator/pvt.js.map +1 -1
- package/dist/engine/renderers/Indicator/roc.js +3 -11
- package/dist/engine/renderers/Indicator/roc.js.map +1 -1
- package/dist/engine/renderers/Indicator/rsi.js +12 -20
- package/dist/engine/renderers/Indicator/rsi.js.map +1 -1
- package/dist/engine/renderers/Indicator/shared/webglBand.d.ts +0 -2
- package/dist/engine/renderers/Indicator/shared/webglBand.d.ts.map +1 -1
- package/dist/engine/renderers/Indicator/shared/webglBand.js +0 -6
- package/dist/engine/renderers/Indicator/shared/webglBand.js.map +1 -1
- package/dist/engine/renderers/Indicator/stoch.js +9 -17
- package/dist/engine/renderers/Indicator/stoch.js.map +1 -1
- package/dist/engine/renderers/Indicator/tema.js +3 -11
- package/dist/engine/renderers/Indicator/tema.js.map +1 -1
- package/dist/engine/renderers/Indicator/trix.js +3 -11
- package/dist/engine/renderers/Indicator/trix.js.map +1 -1
- package/dist/engine/renderers/Indicator/vma.js +3 -11
- package/dist/engine/renderers/Indicator/vma.js.map +1 -1
- package/dist/engine/renderers/Indicator/vwap.js +3 -11
- package/dist/engine/renderers/Indicator/vwap.js.map +1 -1
- package/dist/engine/renderers/Indicator/wma.js +3 -11
- package/dist/engine/renderers/Indicator/wma.js.map +1 -1
- package/dist/engine/renderers/Indicator/wmsr.js +6 -13
- package/dist/engine/renderers/Indicator/wmsr.js.map +1 -1
- package/dist/engine/renderers/candle.d.ts.map +1 -1
- package/dist/engine/renderers/candle.js +21 -28
- package/dist/engine/renderers/candle.js.map +1 -1
- package/dist/engine/renderers/candleViaRenderer.d.ts +19 -0
- package/dist/engine/renderers/candleViaRenderer.d.ts.map +1 -0
- package/dist/engine/renderers/candleViaRenderer.js +15 -0
- package/dist/engine/renderers/candleViaRenderer.js.map +1 -0
- package/dist/engine/renderers/linesViaRenderer.d.ts +55 -0
- package/dist/engine/renderers/linesViaRenderer.d.ts.map +1 -0
- package/dist/engine/renderers/linesViaRenderer.js +169 -0
- package/dist/engine/renderers/linesViaRenderer.js.map +1 -0
- package/dist/engine/renderers/rectsViaRenderer.d.ts +18 -0
- package/dist/engine/renderers/rectsViaRenderer.d.ts.map +1 -0
- package/dist/engine/renderers/rectsViaRenderer.js +63 -0
- package/dist/engine/renderers/rectsViaRenderer.js.map +1 -0
- package/dist/engine/renderers/subVolume.js +7 -26
- package/dist/engine/renderers/subVolume.js.map +1 -1
- package/dist/engine/renderers/timeAxis.js +1 -1
- package/dist/engine/renderers/timeAxis.js.map +1 -1
- package/dist/engine/state/chartStateKernel.d.ts +11 -2
- package/dist/engine/state/chartStateKernel.d.ts.map +1 -1
- package/dist/engine/state/chartStateKernel.js +28 -12
- package/dist/engine/state/chartStateKernel.js.map +1 -1
- package/dist/engine/state/index.d.ts +1 -1
- package/dist/engine/state/index.d.ts.map +1 -1
- package/dist/engine/state/index.js +1 -1
- package/dist/engine/state/index.js.map +1 -1
- package/dist/engine/state/interactionState.d.ts +15 -7
- package/dist/engine/state/interactionState.d.ts.map +1 -1
- package/dist/engine/state/interactionState.js +55 -60
- package/dist/engine/state/interactionState.js.map +1 -1
- package/dist/engine/state/rendererState.d.ts +14 -0
- package/dist/engine/state/rendererState.d.ts.map +1 -0
- package/dist/engine/state/rendererState.js +25 -0
- package/dist/engine/state/rendererState.js.map +1 -0
- package/dist/engine/state/themeState.d.ts +11 -4
- package/dist/engine/state/themeState.d.ts.map +1 -1
- package/dist/engine/state/themeState.js +13 -5
- package/dist/engine/state/themeState.js.map +1 -1
- package/dist/engine/state/viewportState.d.ts.map +1 -1
- package/dist/engine/state/viewportState.js +7 -3
- package/dist/engine/state/viewportState.js.map +1 -1
- package/dist/engine/subPaneManager.d.ts.map +1 -1
- package/dist/engine/subPaneManager.js +2 -10
- package/dist/engine/subPaneManager.js.map +1 -1
- package/dist/foundation/config/chartSettings.d.ts +16 -4
- package/dist/foundation/config/chartSettings.d.ts.map +1 -1
- package/dist/foundation/config/chartSettings.js +21 -6
- package/dist/foundation/config/chartSettings.js.map +1 -1
- package/dist/foundation/plugin/index.d.ts +0 -1
- package/dist/foundation/plugin/index.d.ts.map +1 -1
- package/dist/foundation/plugin/rendererPluginManager.d.ts +1 -22
- package/dist/foundation/plugin/rendererPluginManager.d.ts.map +1 -1
- package/dist/foundation/plugin/rendererPluginManager.js +0 -84
- package/dist/foundation/plugin/rendererPluginManager.js.map +1 -1
- package/dist/foundation/plugin/types.d.ts +11 -17
- package/dist/foundation/plugin/types.d.ts.map +1 -1
- package/dist/foundation/plugin/types.js.map +1 -1
- package/dist/foundation/reactivity/frameTransaction.d.ts +71 -0
- package/dist/foundation/reactivity/frameTransaction.d.ts.map +1 -0
- package/dist/foundation/reactivity/frameTransaction.js +170 -0
- package/dist/foundation/reactivity/frameTransaction.js.map +1 -0
- package/dist/foundation/reactivity/index.d.ts +4 -2
- package/dist/foundation/reactivity/index.d.ts.map +1 -1
- package/dist/foundation/reactivity/index.js +2 -1
- package/dist/foundation/reactivity/index.js.map +1 -1
- package/dist/foundation/reactivity/signal.d.ts +20 -0
- package/dist/foundation/reactivity/signal.d.ts.map +1 -1
- package/dist/foundation/reactivity/signal.js +27 -0
- package/dist/foundation/reactivity/signal.js.map +1 -1
- package/dist/foundation/tokens/theme-dark.d.ts.map +1 -1
- package/dist/foundation/tokens/theme-dark.js +2 -1
- package/dist/foundation/tokens/theme-dark.js.map +1 -1
- package/dist/foundation/tokens/theme-light.d.ts.map +1 -1
- package/dist/foundation/tokens/theme-light.js +2 -1
- package/dist/foundation/tokens/theme-light.js.map +1 -1
- package/dist/rendering/render/Renderer.d.ts +29 -14
- package/dist/rendering/render/Renderer.d.ts.map +1 -1
- package/dist/rendering/render/Renderer.js +3 -10
- package/dist/rendering/render/Renderer.js.map +1 -1
- package/dist/rendering/render/SurfaceBackend.d.ts +5 -7
- package/dist/rendering/render/SurfaceBackend.d.ts.map +1 -1
- package/dist/rendering/render/SurfaceBackend.js +5 -7
- package/dist/rendering/render/SurfaceBackend.js.map +1 -1
- package/dist/rendering/render/createCanvas2DRenderer.d.ts +3 -0
- package/dist/rendering/render/createCanvas2DRenderer.d.ts.map +1 -0
- package/dist/rendering/render/createCanvas2DRenderer.js +74 -0
- package/dist/rendering/render/createCanvas2DRenderer.js.map +1 -0
- package/dist/rendering/render/createDefaultRendererHost.d.ts +4 -0
- package/dist/rendering/render/createDefaultRendererHost.d.ts.map +1 -0
- package/dist/rendering/render/createDefaultRendererHost.js +54 -0
- package/dist/rendering/render/createDefaultRendererHost.js.map +1 -0
- package/dist/rendering/render/createWebGLRenderer.d.ts.map +1 -1
- package/dist/rendering/render/createWebGLRenderer.js +42 -80
- package/dist/rendering/render/createWebGLRenderer.js.map +1 -1
- package/dist/rendering/render/createWebGPURenderer.d.ts +11 -0
- package/dist/rendering/render/createWebGPURenderer.d.ts.map +1 -0
- package/dist/rendering/render/createWebGPURenderer.js +619 -0
- package/dist/rendering/render/createWebGPURenderer.js.map +1 -0
- package/dist/rendering/render/createWebGPUSurfaceBackend.d.ts +15 -0
- package/dist/rendering/render/createWebGPUSurfaceBackend.d.ts.map +1 -0
- package/dist/rendering/render/createWebGPUSurfaceBackend.js +92 -0
- package/dist/rendering/render/createWebGPUSurfaceBackend.js.map +1 -0
- package/dist/rendering/render/frameMetrics.d.ts +19 -0
- package/dist/rendering/render/frameMetrics.d.ts.map +1 -0
- package/dist/rendering/render/frameMetrics.js +43 -0
- package/dist/rendering/render/frameMetrics.js.map +1 -0
- package/dist/rendering/render/index.d.ts +10 -0
- package/dist/rendering/render/index.d.ts.map +1 -1
- package/dist/rendering/render/index.js +6 -0
- package/dist/rendering/render/index.js.map +1 -1
- package/dist/rendering/render/rendererHost.d.ts +34 -0
- package/dist/rendering/render/rendererHost.d.ts.map +1 -0
- package/dist/rendering/render/rendererHost.js +144 -0
- package/dist/rendering/render/rendererHost.js.map +1 -0
- package/dist/rendering/render/webgpuResourceTable.d.ts +25 -0
- package/dist/rendering/render/webgpuResourceTable.d.ts.map +1 -0
- package/dist/rendering/render/webgpuResourceTable.js +54 -0
- package/dist/rendering/render/webgpuResourceTable.js.map +1 -0
- package/dist/rendering/scene/createLayerFromPlugin.d.ts.map +1 -1
- package/dist/rendering/scene/createLayerFromPlugin.js +10 -2
- package/dist/rendering/scene/createLayerFromPlugin.js.map +1 -1
- package/dist/rendering/scene/layerRegistry.d.ts +3 -5
- package/dist/rendering/scene/layerRegistry.d.ts.map +1 -1
- package/dist/rendering/scene/layerRegistry.js +1 -1
- package/dist/rendering/scene/layerRegistry.js.map +1 -1
- package/dist/rendering/scene/retainedScene.d.ts +60 -0
- package/dist/rendering/scene/retainedScene.d.ts.map +1 -0
- package/dist/rendering/scene/retainedScene.js +42 -0
- package/dist/rendering/scene/retainedScene.js.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -1
- package/src/__tests__/signal.test.ts +47 -1
- package/src/controllers/__tests__/createChartController.theme.test.ts +102 -0
- package/src/controllers/createChartController.ts +36 -12
- package/src/controllers/index.ts +5 -0
- package/src/controllers/types.ts +14 -0
- package/src/engine/__tests__/chart.dpr.test.ts +64 -0
- package/src/engine/__tests__/paneRenderer.resize.test.ts +0 -7
- package/src/engine/__tests__/renderSinglePath.test.ts +121 -0
- package/src/engine/chart.ts +172 -25
- package/src/engine/controller/__tests__/interaction.dpr.test.ts +45 -41
- package/src/engine/controller/interaction.ts +81 -30
- package/src/engine/data/__tests__/chartDataManager.incrementalLoad.test.ts +1 -0
- package/src/engine/data/incrementalLoadHint.ts +6 -1
- package/src/engine/drawing/DrawingState.ts +107 -89
- package/src/engine/drawing/__tests__/drawingState.workCopy.test.ts +95 -36
- package/src/engine/drawing/__tests__/drawingStore.projection.test.ts +22 -0
- package/src/engine/drawing/index.ts +12 -4
- package/src/engine/drawing/interaction.ts +25 -40
- package/src/engine/indicators/chartIndicatorManager.ts +6 -16
- package/src/engine/layout/chartPaneLayout.ts +6 -11
- package/src/engine/layout/pane.ts +0 -44
- package/src/engine/paneRenderer.ts +3 -34
- package/src/engine/render/chartRenderer.ts +181 -81
- package/src/engine/renderers/Indicator/atr.ts +5 -17
- package/src/engine/renderers/Indicator/boll.ts +15 -26
- package/src/engine/renderers/Indicator/cci.ts +7 -17
- package/src/engine/renderers/Indicator/chaikinVol.ts +3 -15
- package/src/engine/renderers/Indicator/cmf.ts +3 -15
- package/src/engine/renderers/Indicator/dema.ts +3 -15
- package/src/engine/renderers/Indicator/donchian.ts +3 -12
- package/src/engine/renderers/Indicator/ene.ts +15 -25
- package/src/engine/renderers/Indicator/expma.ts +4 -13
- package/src/engine/renderers/Indicator/fastk.ts +7 -17
- package/src/engine/renderers/Indicator/fib.ts +3 -12
- package/src/engine/renderers/Indicator/hma.ts +3 -15
- package/src/engine/renderers/Indicator/hv.ts +3 -15
- package/src/engine/renderers/Indicator/ichimoku.ts +21 -24
- package/src/engine/renderers/Indicator/kama.ts +3 -15
- package/src/engine/renderers/Indicator/keltner.ts +3 -12
- package/src/engine/renderers/Indicator/kst.ts +9 -20
- package/src/engine/renderers/Indicator/ma.ts +9 -21
- package/src/engine/renderers/Indicator/macd.ts +27 -102
- package/src/engine/renderers/Indicator/mfi.ts +3 -15
- package/src/engine/renderers/Indicator/mom.ts +7 -17
- package/src/engine/renderers/Indicator/obv.ts +3 -15
- package/src/engine/renderers/Indicator/parkinson.ts +3 -15
- package/src/engine/renderers/Indicator/pvt.ts +3 -15
- package/src/engine/renderers/Indicator/roc.ts +3 -15
- package/src/engine/renderers/Indicator/rsi.ts +12 -23
- package/src/engine/renderers/Indicator/shared/webglBand.ts +0 -13
- package/src/engine/renderers/Indicator/stoch.ts +9 -20
- package/src/engine/renderers/Indicator/tema.ts +3 -15
- package/src/engine/renderers/Indicator/trix.ts +3 -12
- package/src/engine/renderers/Indicator/vma.ts +3 -15
- package/src/engine/renderers/Indicator/vwap.ts +3 -15
- package/src/engine/renderers/Indicator/wma.ts +3 -15
- package/src/engine/renderers/Indicator/wmsr.ts +7 -17
- package/src/engine/renderers/__tests__/candle.sceneRenderer.test.ts +208 -0
- package/src/engine/renderers/__tests__/candleViaRenderer.test.ts +100 -0
- package/src/engine/renderers/__tests__/linesViaRenderer.test.ts +263 -0
- package/src/engine/renderers/__tests__/rectsViaRenderer.test.ts +87 -0
- package/src/engine/renderers/candle.ts +26 -69
- package/src/engine/renderers/candleViaRenderer.ts +38 -0
- package/src/engine/renderers/linesViaRenderer.ts +226 -0
- package/src/engine/renderers/rectsViaRenderer.ts +83 -0
- package/src/engine/renderers/subVolume.ts +9 -61
- package/src/engine/renderers/timeAxis.ts +1 -1
- package/src/engine/state/__tests__/effectiveTheme.test.ts +49 -0
- package/src/engine/state/__tests__/rendererState.test.ts +36 -0
- package/src/engine/state/__tests__/settingsState.test.ts +1 -1
- package/src/engine/state/chartStateKernel.ts +38 -17
- package/src/engine/state/index.ts +6 -1
- package/src/engine/state/interactionState.ts +73 -102
- package/src/engine/state/rendererState.ts +32 -0
- package/src/engine/state/themeState.ts +15 -8
- package/src/engine/state/viewportState.ts +6 -3
- package/src/engine/subPaneManager.ts +2 -16
- package/src/foundation/config/__tests__/chartSettings.test.ts +25 -0
- package/src/foundation/config/chartSettings.ts +24 -7
- package/src/foundation/plugin/index.ts +0 -1
- package/src/foundation/plugin/rendererPluginManager.ts +0 -99
- package/src/foundation/plugin/types.ts +11 -18
- package/src/foundation/reactivity/__tests__/frameTransaction.test.ts +306 -0
- package/src/foundation/reactivity/frameTransaction.ts +239 -0
- package/src/foundation/reactivity/index.ts +8 -0
- package/src/foundation/reactivity/signal.ts +39 -0
- package/src/foundation/tokens/__tests__/__snapshots__/baseline.test.ts.snap +2 -2
- package/src/foundation/tokens/theme-dark.ts +2 -1
- package/src/foundation/tokens/theme-light.ts +2 -1
- package/src/rendering/render/Renderer.ts +27 -14
- package/src/rendering/render/SurfaceBackend.ts +5 -7
- package/src/rendering/render/__tests__/canvas2DRenderer.test.ts +34 -0
- package/src/rendering/render/__tests__/contract.test.ts +2 -2
- package/src/rendering/render/__tests__/frameMetrics.test.ts +24 -0
- package/src/rendering/render/__tests__/rendererHost.test.ts +157 -0
- package/src/rendering/render/__tests__/webglRenderer.fallback.test.ts +14 -74
- package/src/rendering/render/__tests__/webglRenderer.test.ts +64 -6
- package/src/rendering/render/__tests__/webgpuRenderer.test.ts +370 -0
- package/src/rendering/render/__tests__/webgpuResourceTable.test.ts +62 -0
- package/src/rendering/render/__tests__/webgpuSurfaceBackend.test.ts +120 -0
- package/src/rendering/render/createCanvas2DRenderer.ts +83 -0
- package/src/rendering/render/createDefaultRendererHost.ts +74 -0
- package/src/rendering/render/createWebGLRenderer.ts +41 -87
- package/src/rendering/render/createWebGPURenderer.ts +677 -0
- package/src/rendering/render/createWebGPUSurfaceBackend.ts +106 -0
- package/src/rendering/render/frameMetrics.ts +54 -0
- package/src/rendering/render/index.ts +30 -0
- package/src/rendering/render/rendererHost.ts +210 -0
- package/src/rendering/render/webgpuResourceTable.ts +87 -0
- package/src/rendering/scene/__tests__/layerFromPlugin.test.ts +29 -2
- package/src/rendering/scene/__tests__/retainedScene.test.ts +59 -0
- package/src/rendering/scene/createLayerFromPlugin.ts +9 -2
- package/src/rendering/scene/layerRegistry.ts +4 -6
- package/src/rendering/scene/retainedScene.ts +101 -0
- package/src/version.ts +1 -1
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { createFrameTransaction } from '../frameTransaction'
|
|
4
|
+
|
|
5
|
+
type Input = { x: number; y: number }
|
|
6
|
+
type Snapshot = { generation: number; x: number; y: number; sum: number }
|
|
7
|
+
|
|
8
|
+
describe('createFrameTransaction', () => {
|
|
9
|
+
it('coalesces many writeInput into one published snapshot on flush', () => {
|
|
10
|
+
const ft = createFrameTransaction<Input, Snapshot>({
|
|
11
|
+
initialInput: { x: 0, y: 0 },
|
|
12
|
+
derive: (input, generation) => ({
|
|
13
|
+
generation,
|
|
14
|
+
x: input.x,
|
|
15
|
+
y: input.y,
|
|
16
|
+
sum: input.x + input.y,
|
|
17
|
+
}),
|
|
18
|
+
})
|
|
19
|
+
const listener = vi.fn()
|
|
20
|
+
ft.published$.subscribe(listener)
|
|
21
|
+
|
|
22
|
+
ft.writeInput({ x: 1 })
|
|
23
|
+
ft.writeInput({ x: 2, y: 3 })
|
|
24
|
+
ft.writeInput({ x: 10 })
|
|
25
|
+
expect(listener).not.toHaveBeenCalled()
|
|
26
|
+
|
|
27
|
+
const snap = ft.flush()
|
|
28
|
+
expect(snap).toEqual({ generation: 1, x: 10, y: 3, sum: 13 })
|
|
29
|
+
expect(ft.published$.peek()).toEqual(snap)
|
|
30
|
+
expect(listener).toHaveBeenCalledTimes(1)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('bumps generation only on successful flush', () => {
|
|
34
|
+
const ft = createFrameTransaction<Input, Snapshot>({
|
|
35
|
+
initialInput: { x: 0, y: 0 },
|
|
36
|
+
derive: (input, generation) => ({
|
|
37
|
+
generation,
|
|
38
|
+
x: input.x,
|
|
39
|
+
y: input.y,
|
|
40
|
+
sum: input.x + input.y,
|
|
41
|
+
}),
|
|
42
|
+
})
|
|
43
|
+
expect(ft.generation).toBe(0)
|
|
44
|
+
ft.writeInput({ x: 1 })
|
|
45
|
+
ft.flush()
|
|
46
|
+
expect(ft.generation).toBe(1)
|
|
47
|
+
ft.writeInput({ y: 2 })
|
|
48
|
+
ft.flush()
|
|
49
|
+
expect(ft.generation).toBe(2)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('does not publish when derive throws; generation stays', () => {
|
|
53
|
+
let fail = false
|
|
54
|
+
const ft = createFrameTransaction<Input, Snapshot>({
|
|
55
|
+
initialInput: { x: 0, y: 0 },
|
|
56
|
+
derive: (input, generation) => {
|
|
57
|
+
if (fail) throw new Error('derive-fail')
|
|
58
|
+
return { generation, x: input.x, y: input.y, sum: input.x + input.y }
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
fail = true
|
|
62
|
+
ft.writeInput({ x: 5 })
|
|
63
|
+
expect(() => ft.flush()).toThrow('derive-fail')
|
|
64
|
+
expect(ft.generation).toBe(0)
|
|
65
|
+
expect(ft.published$.peek().generation).toBe(0)
|
|
66
|
+
|
|
67
|
+
fail = false
|
|
68
|
+
// 失败帧应保留 pending,无需再次 write 也能重试
|
|
69
|
+
const snap = ft.flush()
|
|
70
|
+
expect(snap.generation).toBe(1)
|
|
71
|
+
expect(snap.x).toBe(5)
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('routes writeInput during render into next generation only', () => {
|
|
75
|
+
const seen: number[] = []
|
|
76
|
+
const ft = createFrameTransaction<Input, Snapshot>({
|
|
77
|
+
initialInput: { x: 0, y: 0 },
|
|
78
|
+
derive: (input, generation) => ({
|
|
79
|
+
generation,
|
|
80
|
+
x: input.x,
|
|
81
|
+
y: input.y,
|
|
82
|
+
sum: input.x + input.y,
|
|
83
|
+
}),
|
|
84
|
+
render: (snapshot) => {
|
|
85
|
+
seen.push(snapshot.x)
|
|
86
|
+
// re-entrant high-frequency write during paint
|
|
87
|
+
ft.writeInput({ x: 99 })
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
ft.writeInput({ x: 1 })
|
|
91
|
+
const first = ft.flush()
|
|
92
|
+
expect(first.x).toBe(1)
|
|
93
|
+
expect(seen).toEqual([1])
|
|
94
|
+
// re-entrant write not published yet
|
|
95
|
+
expect(ft.published$.peek().x).toBe(1)
|
|
96
|
+
const second = ft.flush()
|
|
97
|
+
expect(second.x).toBe(99)
|
|
98
|
+
expect(second.generation).toBe(2)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('flush with no pending input returns published snapshot without notify', () => {
|
|
102
|
+
const ft = createFrameTransaction<Input, Snapshot>({
|
|
103
|
+
initialInput: { x: 1, y: 2 },
|
|
104
|
+
derive: (input, generation) => ({
|
|
105
|
+
generation,
|
|
106
|
+
x: input.x,
|
|
107
|
+
y: input.y,
|
|
108
|
+
sum: input.x + input.y,
|
|
109
|
+
}),
|
|
110
|
+
})
|
|
111
|
+
// seed first publish
|
|
112
|
+
ft.writeInput({ x: 1, y: 2 })
|
|
113
|
+
ft.flush()
|
|
114
|
+
const listener = vi.fn()
|
|
115
|
+
ft.published$.subscribe(listener)
|
|
116
|
+
const again = ft.flush()
|
|
117
|
+
expect(again.generation).toBe(1)
|
|
118
|
+
expect(listener).not.toHaveBeenCalled()
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('select publishes only when selected value changes by equality', () => {
|
|
122
|
+
const ft = createFrameTransaction<Input, Snapshot>({
|
|
123
|
+
initialInput: { x: 0, y: 0 },
|
|
124
|
+
derive: (input, generation) => ({
|
|
125
|
+
generation,
|
|
126
|
+
x: input.x,
|
|
127
|
+
y: input.y,
|
|
128
|
+
sum: input.x + input.y,
|
|
129
|
+
}),
|
|
130
|
+
})
|
|
131
|
+
const selected = ft.select((s) => s.x)
|
|
132
|
+
const listener = vi.fn()
|
|
133
|
+
selected.subscribe(listener)
|
|
134
|
+
|
|
135
|
+
ft.writeInput({ x: 1, y: 0 })
|
|
136
|
+
ft.flush()
|
|
137
|
+
expect(listener).toHaveBeenCalledTimes(1)
|
|
138
|
+
expect(selected.peek()).toBe(1)
|
|
139
|
+
|
|
140
|
+
ft.writeInput({ x: 1, y: 9 })
|
|
141
|
+
ft.flush()
|
|
142
|
+
// x unchanged
|
|
143
|
+
expect(listener).toHaveBeenCalledTimes(1)
|
|
144
|
+
|
|
145
|
+
ft.writeInput({ x: 2 })
|
|
146
|
+
ft.flush()
|
|
147
|
+
expect(listener).toHaveBeenCalledTimes(2)
|
|
148
|
+
expect(selected.peek()).toBe(2)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('scheduleFlush coalesces to one raf-style runner', () => {
|
|
152
|
+
const runners: Array<() => void> = []
|
|
153
|
+
const schedule = (run: () => void) => {
|
|
154
|
+
runners.push(run)
|
|
155
|
+
return 1
|
|
156
|
+
}
|
|
157
|
+
const ft = createFrameTransaction<Input, Snapshot>({
|
|
158
|
+
initialInput: { x: 0, y: 0 },
|
|
159
|
+
derive: (input, generation) => ({
|
|
160
|
+
generation,
|
|
161
|
+
x: input.x,
|
|
162
|
+
y: input.y,
|
|
163
|
+
sum: input.x + input.y,
|
|
164
|
+
}),
|
|
165
|
+
schedule,
|
|
166
|
+
})
|
|
167
|
+
ft.writeInput({ x: 1 })
|
|
168
|
+
ft.scheduleFlush()
|
|
169
|
+
ft.writeInput({ x: 2 })
|
|
170
|
+
ft.scheduleFlush()
|
|
171
|
+
expect(runners).toHaveLength(1)
|
|
172
|
+
runners[0]!()
|
|
173
|
+
expect(ft.published$.peek().x).toBe(2)
|
|
174
|
+
expect(ft.generation).toBe(1)
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('advances generation before notifying published subscribers', () => {
|
|
178
|
+
const ft = createFrameTransaction<Input, Snapshot>({
|
|
179
|
+
initialInput: { x: 0, y: 0 },
|
|
180
|
+
derive: (input, generation) => ({
|
|
181
|
+
generation,
|
|
182
|
+
x: input.x,
|
|
183
|
+
y: input.y,
|
|
184
|
+
sum: input.x + input.y,
|
|
185
|
+
}),
|
|
186
|
+
})
|
|
187
|
+
let seenGeneration = -1
|
|
188
|
+
ft.published$.subscribe(() => {
|
|
189
|
+
seenGeneration = ft.generation
|
|
190
|
+
})
|
|
191
|
+
ft.writeInput({ x: 1 })
|
|
192
|
+
ft.flush()
|
|
193
|
+
expect(seenGeneration).toBe(1)
|
|
194
|
+
expect(ft.published$.peek().generation).toBe(1)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('defers re-entrant flush during publish to next generation only', () => {
|
|
198
|
+
const order: number[] = []
|
|
199
|
+
const ft = createFrameTransaction<Input, Snapshot>({
|
|
200
|
+
initialInput: { x: 0, y: 0 },
|
|
201
|
+
derive: (input, generation) => ({
|
|
202
|
+
generation,
|
|
203
|
+
x: input.x,
|
|
204
|
+
y: input.y,
|
|
205
|
+
sum: input.x + input.y,
|
|
206
|
+
}),
|
|
207
|
+
})
|
|
208
|
+
ft.published$.subscribe(() => {
|
|
209
|
+
const snap = ft.published$.peek()
|
|
210
|
+
order.push(snap.x)
|
|
211
|
+
if (snap.x === 1) {
|
|
212
|
+
ft.writeInput({ x: 2 })
|
|
213
|
+
// re-entrant flush must not publish x=2 before remaining listeners of gen1
|
|
214
|
+
ft.flush()
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
const late = vi.fn()
|
|
218
|
+
ft.published$.subscribe(late)
|
|
219
|
+
|
|
220
|
+
ft.writeInput({ x: 1 })
|
|
221
|
+
ft.flush()
|
|
222
|
+
expect(order).toEqual([1])
|
|
223
|
+
expect(late).toHaveBeenCalledTimes(1)
|
|
224
|
+
expect(ft.published$.peek().x).toBe(1)
|
|
225
|
+
expect(ft.generation).toBe(1)
|
|
226
|
+
|
|
227
|
+
// deferred work runs on next idle flush
|
|
228
|
+
const second = ft.flush()
|
|
229
|
+
expect(second.x).toBe(2)
|
|
230
|
+
expect(second.generation).toBe(2)
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
it('freezes generation-0 published snapshot root', () => {
|
|
234
|
+
const ft = createFrameTransaction<Input, Snapshot>({
|
|
235
|
+
initialInput: { x: 0, y: 0 },
|
|
236
|
+
derive: (input, generation) => ({
|
|
237
|
+
generation,
|
|
238
|
+
x: input.x,
|
|
239
|
+
y: input.y,
|
|
240
|
+
sum: input.x + input.y,
|
|
241
|
+
}),
|
|
242
|
+
})
|
|
243
|
+
const snap = ft.published$.peek() as Snapshot & { x: number }
|
|
244
|
+
expect(Object.isFrozen(snap)).toBe(true)
|
|
245
|
+
expect(() => {
|
|
246
|
+
;(snap as { x: number }).x = 99
|
|
247
|
+
}).toThrow()
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('reschedules after scheduled flush throws', () => {
|
|
251
|
+
const runners: Array<() => void> = []
|
|
252
|
+
let failNextRealFrame = false
|
|
253
|
+
const ft = createFrameTransaction<Input, Snapshot>({
|
|
254
|
+
initialInput: { x: 0, y: 0 },
|
|
255
|
+
derive: (input, generation) => {
|
|
256
|
+
// generation 0 是构造占位,不可失败
|
|
257
|
+
if (generation > 0 && failNextRealFrame) {
|
|
258
|
+
failNextRealFrame = false
|
|
259
|
+
throw new Error('boom')
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
generation,
|
|
263
|
+
x: input.x,
|
|
264
|
+
y: input.y,
|
|
265
|
+
sum: input.x + input.y,
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
schedule: (run) => {
|
|
269
|
+
runners.push(run)
|
|
270
|
+
return 1
|
|
271
|
+
},
|
|
272
|
+
})
|
|
273
|
+
failNextRealFrame = true
|
|
274
|
+
ft.writeInput({ x: 3 })
|
|
275
|
+
ft.scheduleFlush()
|
|
276
|
+
expect(runners).toHaveLength(1)
|
|
277
|
+
expect(() => runners[0]!()).toThrow('boom')
|
|
278
|
+
// dirty retained → another schedule registered
|
|
279
|
+
expect(runners.length).toBeGreaterThanOrEqual(2)
|
|
280
|
+
runners[runners.length - 1]!()
|
|
281
|
+
expect(ft.published$.peek().x).toBe(3)
|
|
282
|
+
expect(ft.generation).toBe(1)
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('resets scheduleQueued when schedule() throws', () => {
|
|
286
|
+
let shouldThrow = true
|
|
287
|
+
const ft = createFrameTransaction<Input, Snapshot>({
|
|
288
|
+
initialInput: { x: 0, y: 0 },
|
|
289
|
+
derive: (input, generation) => ({
|
|
290
|
+
generation,
|
|
291
|
+
x: input.x,
|
|
292
|
+
y: input.y,
|
|
293
|
+
sum: input.x + input.y,
|
|
294
|
+
}),
|
|
295
|
+
schedule: () => {
|
|
296
|
+
if (shouldThrow) throw new Error('no-raf')
|
|
297
|
+
return 1
|
|
298
|
+
},
|
|
299
|
+
})
|
|
300
|
+
ft.writeInput({ x: 1 })
|
|
301
|
+
expect(() => ft.scheduleFlush()).toThrow('no-raf')
|
|
302
|
+
shouldThrow = false
|
|
303
|
+
// must not be permanently stuck
|
|
304
|
+
expect(() => ft.scheduleFlush()).not.toThrow()
|
|
305
|
+
})
|
|
306
|
+
})
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 帧事务:高频输入集中写入,每帧最多发布一次不可变快照。
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* 解决两类问题:
|
|
6
|
+
* 1. pointermove 等事件若直接写普通 Signal,会按事件频率广播订阅者。
|
|
7
|
+
* 2. latest 与 published 双视图会让不同模块读到不同代际状态。
|
|
8
|
+
*
|
|
9
|
+
* 本原语只暴露 pending 写入与 published 快照,不暴露公开 latest。
|
|
10
|
+
* 一帧固定走 capture → derive → seal → render → publish → complete。
|
|
11
|
+
* render 或 publish 期间的 writeInput 一律进入下一代 pending。
|
|
12
|
+
* 非 idle 时调用 flush 不会嵌套发布,仅保留 dirty 供外层完成后调度。
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { createSignal, type ReadonlySignal } from './signal'
|
|
16
|
+
|
|
17
|
+
/** 帧事务所处阶段;用于隔离重入写入 */
|
|
18
|
+
export type FramePhase = 'idle' | 'capturing' | 'deriving' | 'sealing' | 'rendering' | 'publishing'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 浅合并输入补丁。
|
|
22
|
+
* 仅顶层键覆盖,不递归合并嵌套对象。
|
|
23
|
+
*/
|
|
24
|
+
function mergeInput<T extends Record<string, unknown>>(base: T, patch: Partial<T>): T {
|
|
25
|
+
return { ...base, ...patch }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 浅冻结快照根对象;大数组字段由 derive 侧结构共享,禁止深拷贝 */
|
|
29
|
+
function sealSnapshotRoot<T>(snapshot: T): T {
|
|
30
|
+
if (typeof snapshot === 'object' && snapshot !== null) {
|
|
31
|
+
Object.freeze(snapshot)
|
|
32
|
+
}
|
|
33
|
+
return snapshot
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface FrameTransactionOptions<TInput extends Record<string, unknown>, TSnapshot> {
|
|
37
|
+
/** 初始 pending 输入;也会用于生成 generation 0 的占位 published */
|
|
38
|
+
initialInput: TInput
|
|
39
|
+
/**
|
|
40
|
+
* 由封存输入纯推导快照。
|
|
41
|
+
* 不得写外部 kernel / DOM;失败则本帧不发布。
|
|
42
|
+
*/
|
|
43
|
+
derive: (input: Readonly<TInput>, generation: number) => TSnapshot
|
|
44
|
+
/**
|
|
45
|
+
* 可选:使用本帧快照绘制或执行副作用。
|
|
46
|
+
* 此阶段 writeInput 进入下一代,不得假定能改当前快照。
|
|
47
|
+
*/
|
|
48
|
+
render?: (snapshot: Readonly<TSnapshot>) => void
|
|
49
|
+
/**
|
|
50
|
+
* 调度 flush 的宿主。默认 requestAnimationFrame;测试可注入同步队列。
|
|
51
|
+
* 返回值可忽略(兼容 rAF handle)。
|
|
52
|
+
*/
|
|
53
|
+
schedule?: (run: () => void) => unknown
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface FrameTransaction<TInput extends Record<string, unknown>, TSnapshot> {
|
|
57
|
+
/** 最近一次成功发布的快照(只读 Signal) */
|
|
58
|
+
readonly published$: ReadonlySignal<Readonly<TSnapshot>>
|
|
59
|
+
/** 已成功发布的帧代际;失败 flush 不增加 */
|
|
60
|
+
readonly generation: number
|
|
61
|
+
/** 当前阶段,调试与不变量检查用 */
|
|
62
|
+
readonly phase: FramePhase
|
|
63
|
+
/**
|
|
64
|
+
* 合并高频输入。idle 写入当前 pending;非 idle 写入 nextPending。
|
|
65
|
+
* 不触发订阅通知。
|
|
66
|
+
*/
|
|
67
|
+
writeInput(patch: Partial<TInput>): void
|
|
68
|
+
/**
|
|
69
|
+
* 同步执行一帧事务。无 pending 时返回当前 published,且不通知。
|
|
70
|
+
* 非 idle 调用不会嵌套发布,返回当前 published。
|
|
71
|
+
* @returns 本帧使用的快照(成功时等于 published$.peek())
|
|
72
|
+
*/
|
|
73
|
+
flush(): Readonly<TSnapshot>
|
|
74
|
+
/**
|
|
75
|
+
* 请求在宿主调度器上合并 flush;多次调用在同一 pending 调度内只注册一次。
|
|
76
|
+
*/
|
|
77
|
+
scheduleFlush(): void
|
|
78
|
+
/**
|
|
79
|
+
* 从 published 投影只读 Signal。
|
|
80
|
+
* 仅当 select 结果相对上一值 Object.is 不等时通知。
|
|
81
|
+
*/
|
|
82
|
+
select<T>(selector: (snapshot: Readonly<TSnapshot>) => T): ReadonlySignal<T>
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 创建帧事务控制器。
|
|
87
|
+
*
|
|
88
|
+
* @typeParam TInput - 可合并的输入形状(浅层 Partial 合并)
|
|
89
|
+
* @typeParam TSnapshot - derive 产出的不可变快照类型
|
|
90
|
+
*/
|
|
91
|
+
export function createFrameTransaction<
|
|
92
|
+
TInput extends Record<string, unknown>,
|
|
93
|
+
TSnapshot,
|
|
94
|
+
>(options: FrameTransactionOptions<TInput, TSnapshot>): FrameTransaction<TInput, TSnapshot> {
|
|
95
|
+
const { derive, render } = options
|
|
96
|
+
const schedule =
|
|
97
|
+
options.schedule ??
|
|
98
|
+
((run: () => void) => {
|
|
99
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
100
|
+
return requestAnimationFrame(run)
|
|
101
|
+
}
|
|
102
|
+
return setTimeout(run, 0)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
/** 当前代可写入的输入(仅 idle 时接收 writeInput) */
|
|
106
|
+
let pending: TInput = { ...options.initialInput }
|
|
107
|
+
/** 事务进行中产生的输入,complete 后并入下一代 */
|
|
108
|
+
let nextPending: TInput | null = null
|
|
109
|
+
/** 是否存在尚未 flush 的 pending 变更 */
|
|
110
|
+
let dirty = false
|
|
111
|
+
let generation = 0
|
|
112
|
+
let phase: FramePhase = 'idle'
|
|
113
|
+
let scheduleQueued = false
|
|
114
|
+
|
|
115
|
+
// generation 0:用初始输入 derive 一次,作为 published 占位,避免订阅者读到 undefined
|
|
116
|
+
const initialSnapshot = sealSnapshotRoot(derive({ ...pending }, 0))
|
|
117
|
+
const published = createSignal<TSnapshot>(initialSnapshot)
|
|
118
|
+
|
|
119
|
+
function writeInput(patch: Partial<TInput>): void {
|
|
120
|
+
if (phase === 'idle') {
|
|
121
|
+
pending = mergeInput(pending, patch)
|
|
122
|
+
dirty = true
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
// capturing 之后当前输入已封存;重入写入只能进下一代
|
|
126
|
+
const base = nextPending ?? pending
|
|
127
|
+
nextPending = mergeInput(base, patch)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function flush(): Readonly<TSnapshot> {
|
|
131
|
+
// 禁止嵌套发布:订阅者 / render 中 flush 只保留 dirty,由外层 complete 后再调度
|
|
132
|
+
if (phase !== 'idle') {
|
|
133
|
+
return published.peek()
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (!dirty && nextPending === null) {
|
|
137
|
+
return published.peek()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 若仅有 nextPending(例如上一帧 render 中写入),提升为当前 pending
|
|
141
|
+
if (!dirty && nextPending !== null) {
|
|
142
|
+
pending = nextPending
|
|
143
|
+
nextPending = null
|
|
144
|
+
dirty = true
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
let sealedInput: TInput | undefined
|
|
148
|
+
phase = 'capturing'
|
|
149
|
+
try {
|
|
150
|
+
sealedInput = pending
|
|
151
|
+
dirty = false
|
|
152
|
+
// 本帧 capture 之后的新写入进入 nextPending,不得污染 sealedInput
|
|
153
|
+
pending = { ...sealedInput }
|
|
154
|
+
|
|
155
|
+
phase = 'deriving'
|
|
156
|
+
const nextGeneration = generation + 1
|
|
157
|
+
const snapshot = derive(sealedInput, nextGeneration)
|
|
158
|
+
|
|
159
|
+
phase = 'sealing'
|
|
160
|
+
sealSnapshotRoot(snapshot)
|
|
161
|
+
|
|
162
|
+
phase = 'rendering'
|
|
163
|
+
render?.(snapshot)
|
|
164
|
+
|
|
165
|
+
// 先推进代际再发布,保证订阅者读 generation 与 snapshot.generation 一致
|
|
166
|
+
phase = 'publishing'
|
|
167
|
+
generation = nextGeneration
|
|
168
|
+
published.set(snapshot)
|
|
169
|
+
|
|
170
|
+
return snapshot
|
|
171
|
+
} catch (err) {
|
|
172
|
+
// derive/render 失败:不推进 generation(若已推进则回滚),保留 sealed 输入供重试
|
|
173
|
+
if (sealedInput !== undefined) {
|
|
174
|
+
pending = sealedInput
|
|
175
|
+
dirty = true
|
|
176
|
+
}
|
|
177
|
+
throw err
|
|
178
|
+
} finally {
|
|
179
|
+
phase = 'idle'
|
|
180
|
+
// 事务中累积的 nextPending 与重试 pending 合并
|
|
181
|
+
if (nextPending !== null) {
|
|
182
|
+
pending = mergeInput(pending, nextPending)
|
|
183
|
+
nextPending = null
|
|
184
|
+
dirty = true
|
|
185
|
+
}
|
|
186
|
+
// 有残留 dirty 时挂下一代调度(含 re-entrant write / 失败重试)
|
|
187
|
+
if (dirty) {
|
|
188
|
+
scheduleFlush()
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function scheduleFlush(): void {
|
|
194
|
+
if (scheduleQueued) return
|
|
195
|
+
if (!dirty && nextPending === null) return
|
|
196
|
+
scheduleQueued = true
|
|
197
|
+
try {
|
|
198
|
+
schedule(() => {
|
|
199
|
+
scheduleQueued = false
|
|
200
|
+
// flush 的 finally 已在失败时保留 dirty 并 scheduleFlush;此处继续抛出供测试/诊断
|
|
201
|
+
flush()
|
|
202
|
+
})
|
|
203
|
+
} catch (err) {
|
|
204
|
+
// schedule 本身失败:复位标志,允许后续 scheduleFlush 重试
|
|
205
|
+
scheduleQueued = false
|
|
206
|
+
throw err
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function select<T>(selector: (snapshot: Readonly<TSnapshot>) => T): ReadonlySignal<T> {
|
|
211
|
+
const selected = createSignal(selector(published.peek()))
|
|
212
|
+
published.subscribe(() => {
|
|
213
|
+
const next = selector(published.peek())
|
|
214
|
+
selected.set(next)
|
|
215
|
+
})
|
|
216
|
+
const read = (): T => selected()
|
|
217
|
+
return Object.assign(read, {
|
|
218
|
+
peek: selected.peek,
|
|
219
|
+
subscribe: selected.subscribe,
|
|
220
|
+
}) as ReadonlySignal<T>
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
published$: Object.assign((() => published()) as ReadonlySignal<Readonly<TSnapshot>>, {
|
|
225
|
+
peek: published.peek,
|
|
226
|
+
subscribe: published.subscribe,
|
|
227
|
+
}),
|
|
228
|
+
get generation() {
|
|
229
|
+
return generation
|
|
230
|
+
},
|
|
231
|
+
get phase() {
|
|
232
|
+
return phase
|
|
233
|
+
},
|
|
234
|
+
writeInput,
|
|
235
|
+
flush,
|
|
236
|
+
scheduleFlush,
|
|
237
|
+
select,
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -4,6 +4,7 @@ export {
|
|
|
4
4
|
computed,
|
|
5
5
|
effect,
|
|
6
6
|
batch,
|
|
7
|
+
selectSignal,
|
|
7
8
|
createStateStore,
|
|
8
9
|
createSubState,
|
|
9
10
|
} from './signal'
|
|
@@ -14,4 +15,11 @@ export type {
|
|
|
14
15
|
WritableRef,
|
|
15
16
|
ReadonlyRef,
|
|
16
17
|
Computed,
|
|
18
|
+
SelectedSignal,
|
|
17
19
|
} from './signal'
|
|
20
|
+
export { createFrameTransaction } from './frameTransaction'
|
|
21
|
+
export type {
|
|
22
|
+
FramePhase,
|
|
23
|
+
FrameTransaction,
|
|
24
|
+
FrameTransactionOptions,
|
|
25
|
+
} from './frameTransaction'
|
|
@@ -233,6 +233,45 @@ export function batch<T>(fn: () => T): T {
|
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
/**
|
|
237
|
+
* selectSignal 返回值:只读投影 + 解除对 source 的订阅。
|
|
238
|
+
*/
|
|
239
|
+
export type SelectedSignal<U> = ReadonlySignal<U> & {
|
|
240
|
+
/** 取消对 source 的订阅;之后不再随 source 更新 */
|
|
241
|
+
dispose: () => void
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* 从源 Signal 投影只读 Signal。
|
|
246
|
+
* 仅当 selector 结果相对上一值 equal 判定为不等时通知。
|
|
247
|
+
*
|
|
248
|
+
* @remarks
|
|
249
|
+
* 用于框架桥接:避免整包 snapshot 变化时无关字段也触发 UI 更新。
|
|
250
|
+
* 默认 equal 为 Object.is;对 {x,y} 等可传入结构相等函数。
|
|
251
|
+
* 短生命周期投影必须调用 dispose,否则会永久订阅 source。
|
|
252
|
+
*
|
|
253
|
+
* @typeParam T - 源快照类型
|
|
254
|
+
* @typeParam U - 投影结果类型
|
|
255
|
+
*/
|
|
256
|
+
export function selectSignal<T, U>(
|
|
257
|
+
source: ReadonlySignal<T>,
|
|
258
|
+
selector: (value: T) => U,
|
|
259
|
+
equal: (a: U, b: U) => boolean = Object.is,
|
|
260
|
+
): SelectedSignal<U> {
|
|
261
|
+
const selected = createSignal(selector(source.peek()))
|
|
262
|
+
const unsubSource = source.subscribe(() => {
|
|
263
|
+
const next = selector(source.peek())
|
|
264
|
+
if (equal(selected.peek(), next)) return
|
|
265
|
+
selected.set(next)
|
|
266
|
+
})
|
|
267
|
+
const read = (): U => selected()
|
|
268
|
+
return Object.assign(read, {
|
|
269
|
+
peek: selected.peek,
|
|
270
|
+
subscribe: selected.subscribe,
|
|
271
|
+
dispose: unsubSource,
|
|
272
|
+
}) as SelectedSignal<U>
|
|
273
|
+
}
|
|
274
|
+
|
|
236
275
|
/**
|
|
237
276
|
* 从初始状态对象创建一组相关 signal。
|
|
238
277
|
*
|
|
@@ -71,7 +71,7 @@ exports[`theme baseline — dark > CSS declaration block (snapshot) 1`] = `
|
|
|
71
71
|
--klc-color-tag-bg-transparent: transparent;
|
|
72
72
|
--klc-color-tag-bg-active: #1890ff;
|
|
73
73
|
--klc-color-tag-bg-active-hover: #40a9ff;
|
|
74
|
-
--klc-color-tag-bg-hover: #
|
|
74
|
+
--klc-color-tag-bg-hover: #2D3544;
|
|
75
75
|
--klc-color-border-dark: rgba(255, 255, 255, 0.15);
|
|
76
76
|
--klc-color-border-medium: rgba(255, 255, 255, 0.12);
|
|
77
77
|
--klc-color-border-light: rgba(255, 255, 255, 0.08);
|
|
@@ -271,7 +271,7 @@ exports[`theme baseline — light > CSS declaration block (snapshot) 1`] = `
|
|
|
271
271
|
--klc-color-tag-bg-transparent: transparent;
|
|
272
272
|
--klc-color-tag-bg-active: #1890ff;
|
|
273
273
|
--klc-color-tag-bg-active-hover: #40a9ff;
|
|
274
|
-
--klc-color-tag-bg-hover: #
|
|
274
|
+
--klc-color-tag-bg-hover: #E5E7EB;
|
|
275
275
|
--klc-color-border-dark: rgba(0, 0, 0, 0.12);
|
|
276
276
|
--klc-color-border-medium: rgba(0, 0, 0, 0.10);
|
|
277
277
|
--klc-color-border-light: rgba(0, 0, 0, 0.08);
|
|
@@ -117,7 +117,8 @@ export const darkTheme: Theme = {
|
|
|
117
117
|
transparent: 'transparent',
|
|
118
118
|
active: '#1890ff',
|
|
119
119
|
activeHover: '#40a9ff',
|
|
120
|
-
hover
|
|
120
|
+
// 相对 background #111827 提高悬停对比,列表项 hover 更易辨认
|
|
121
|
+
hover: '#2D3544',
|
|
121
122
|
},
|
|
122
123
|
border: {
|
|
123
124
|
dark: 'rgba(255, 255, 255, 0.15)',
|
|
@@ -123,7 +123,8 @@ export const lightTheme: Theme = {
|
|
|
123
123
|
transparent: 'transparent',
|
|
124
124
|
active: '#1890ff',
|
|
125
125
|
activeHover: '#40a9ff',
|
|
126
|
-
|
|
126
|
+
// 相对 background #FAFAFA 提高悬停对比,避免与底色糊成一片
|
|
127
|
+
hover: '#E5E7EB',
|
|
127
128
|
},
|
|
128
129
|
border: {
|
|
129
130
|
dark: 'rgba(0, 0, 0, 0.12)',
|