@dailies-studio/shot-editor 0.1.0 → 0.2.0
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/.vite/manifest.json +382 -0
- package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
- package/dist/assets/AgentPanel-DJEphah0.css +1 -0
- package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
- package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
- package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
- package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
- package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
- package/dist/assets/CurvePane-BjFVabKX.css +1 -0
- package/dist/assets/CurvePane-R22WzllJ.js +1 -0
- package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
- package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
- package/dist/assets/FontPicker-v0IOIArM.css +1 -0
- package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
- package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
- package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
- package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
- package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
- package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
- package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
- package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
- package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
- package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
- package/dist/assets/SoundMode-BmcZru3i.js +3 -0
- package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
- package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
- package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
- package/dist/assets/board-B88EPuLO.css +1 -0
- package/dist/assets/board-CL-he7SL.js +3 -0
- package/dist/assets/components-0oOSPN0l.js +1 -0
- package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
- package/dist/assets/googleFonts-BNon6M7X.js +1 -0
- package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
- package/dist/assets/index-4F5NQmI8.css +1 -0
- package/dist/assets/index-BGHQGV5z.js +117 -0
- package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/assets/libraryModel-CpIwfFha.js +1 -0
- package/dist/assets/lucide-6iK64tTB.js +9319 -0
- package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
- package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
- package/dist/assets/webmcp-BsB9gxWC.js +2 -0
- package/dist/index.html +34 -3
- package/package.json +12 -9
- package/src/addFamilies.ts +66 -0
- package/src/api.ts +1631 -55
- package/src/app/App.tsx +977 -198
- package/src/app/lazyPanel.tsx +137 -0
- package/src/app/lazySurfaces.tsx +203 -0
- package/src/audio/index.tsx +41 -10
- package/src/audio/meter.ts +485 -0
- package/src/audio/meterMath.ts +273 -0
- package/src/audio/meterStore.ts +155 -0
- package/src/audio/offlineMix.ts +289 -0
- package/src/audio/preview.ts +928 -118
- package/src/bridge.ts +15 -5
- package/src/bridgeTypes.ts +1611 -36
- package/src/channels.ts +17 -32
- package/src/fonts/googleFonts.ts +4 -0
- package/src/hostApi.ts +174 -0
- package/src/icons.tsx +770 -0
- package/src/layerWords.ts +61 -0
- package/src/main.tsx +56 -4
- package/src/panels/ResizeHandle.tsx +33 -43
- package/src/panels/agent/AgentDots.tsx +36 -0
- package/src/panels/agent/AgentPanel.tsx +119 -43
- package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
- package/src/panels/agent/agent.css +113 -97
- package/src/panels/agent/agentGate.ts +35 -0
- package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
- package/src/panels/code/CodeStudio.tsx +373 -0
- package/src/panels/code/FileTree.tsx +265 -0
- package/src/panels/code/codeBufferGuard.ts +52 -0
- package/src/panels/code/codeParts.tsx +321 -0
- package/src/panels/comments/CommentComposer.tsx +239 -0
- package/src/panels/comments/CommentThread.tsx +346 -0
- package/src/panels/comments/CommentsPanel.tsx +336 -0
- package/src/panels/comments/InboxButton.tsx +131 -0
- package/src/panels/comments/MentionMenu.tsx +98 -0
- package/src/panels/comments/parts.tsx +171 -0
- package/src/panels/export/ExportDialog.tsx +919 -96
- package/src/panels/export/ExportScrim.tsx +57 -0
- package/src/panels/files/AssetsTab.tsx +656 -0
- package/src/panels/files/FilePanel.tsx +1466 -216
- package/src/panels/files/LayerMarks.tsx +40 -0
- package/src/panels/files/LeftColumn.tsx +37 -0
- package/src/panels/files/McpSection.tsx +690 -47
- package/src/panels/files/MissingAssetsNotice.tsx +60 -0
- package/src/panels/files/assetDrag.ts +79 -0
- package/src/panels/files/resourceParts.tsx +255 -0
- package/src/panels/fonts/FontPicker.tsx +762 -0
- package/src/panels/fonts/faceCache.ts +207 -0
- package/src/panels/fonts/fontPicker.css +271 -0
- package/src/panels/fonts/pickerModel.ts +265 -0
- package/src/panels/fonts/pickerRequest.ts +46 -0
- package/src/panels/header/AccountAvatar.tsx +76 -0
- package/src/panels/header/AgentChip.tsx +77 -0
- package/src/panels/header/AppMenu.tsx +213 -0
- package/src/panels/header/Header.tsx +564 -84
- package/src/panels/header/HistoryControls.tsx +31 -5
- package/src/panels/header/PresenceStack.tsx +329 -0
- package/src/panels/header/ProjectTitle.tsx +232 -0
- package/src/panels/header/ShareButton.tsx +59 -0
- package/src/panels/header/StatusBadges.tsx +358 -27
- package/src/panels/header/SyncDeletedDialog.tsx +88 -0
- package/src/panels/header/ZoomControls.tsx +46 -15
- package/src/panels/header/agentHandoff.ts +52 -0
- package/src/panels/header/leave.ts +26 -0
- package/src/panels/help/HelpSection.tsx +189 -163
- package/src/panels/insert/InsertDrawer.tsx +723 -0
- package/src/panels/insert/iconSet.ts +109 -0
- package/src/panels/inspector/ExposeDot.tsx +117 -0
- package/src/panels/inspector/FramingRows.tsx +314 -44
- package/src/panels/inspector/InspectorFooter.tsx +91 -0
- package/src/panels/inspector/InspectorHead.tsx +425 -0
- package/src/panels/inspector/InspectorPanel.tsx +408 -124
- package/src/panels/inspector/KeyframeDot.tsx +373 -0
- package/src/panels/inspector/KeyframeSection.tsx +257 -0
- package/src/panels/inspector/MarkerSection.tsx +55 -11
- package/src/panels/inspector/ParamRow.tsx +84 -53
- package/src/panels/inspector/RightColumn.tsx +152 -0
- package/src/panels/inspector/TimeSection.tsx +866 -0
- package/src/panels/inspector/animate/EaseControl.tsx +244 -0
- package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
- package/src/panels/inspector/animate/ShotSection.tsx +562 -48
- package/src/panels/inspector/design/AlignRow.tsx +95 -31
- package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
- package/src/panels/inspector/design/ClonerSection.tsx +323 -62
- package/src/panels/inspector/design/ComponentSection.tsx +50 -43
- package/src/panels/inspector/design/CornerSection.tsx +45 -14
- package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
- package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
- package/src/panels/inspector/design/DeviceSection.tsx +210 -0
- package/src/panels/inspector/design/FillSection.tsx +83 -51
- package/src/panels/inspector/design/FontField.tsx +196 -0
- package/src/panels/inspector/design/HtmlSection.tsx +23 -12
- package/src/panels/inspector/design/InstanceSection.tsx +373 -154
- package/src/panels/inspector/design/MaskSection.tsx +84 -0
- package/src/panels/inspector/design/MaterialSection.tsx +177 -0
- package/src/panels/inspector/design/MediaSection.tsx +273 -120
- package/src/panels/inspector/design/MeshSection.tsx +910 -82
- package/src/panels/inspector/design/PaintRows.tsx +550 -0
- package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
- package/src/panels/inspector/design/ShadowSection.tsx +221 -48
- package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
- package/src/panels/inspector/design/StrokeSection.tsx +228 -43
- package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
- package/src/panels/inspector/design/TransformSection.tsx +244 -50
- package/src/panels/inspector/design/TypographySection.tsx +679 -110
- package/src/panels/inspector/layout.ts +295 -0
- package/src/panels/inspector/propValue.ts +199 -0
- package/src/panels/inspector/scene/CameraSection.tsx +232 -22
- package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
- package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
- package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
- package/src/panels/inspector/scene/SceneSection.tsx +219 -44
- package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
- package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
- package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
- package/src/panels/inspector/timeModel.ts +190 -0
- package/src/panels/inspector/useDocWrite.ts +438 -146
- package/src/panels/library/LibraryBrowser.tsx +435 -0
- package/src/panels/library/LibraryGrid.tsx +190 -0
- package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
- package/src/panels/library/LibraryRail.tsx +84 -0
- package/src/panels/library/LibrarySoundPanel.tsx +293 -0
- package/src/panels/library/SoundHistory.tsx +245 -0
- package/src/panels/library/StockSounds.tsx +390 -0
- package/src/panels/library/libraryModel.ts +147 -0
- package/src/panels/library/stockSearch.ts +77 -0
- package/src/panels/maskMarks.tsx +117 -0
- package/src/panels/material/MaterialCommentNode.tsx +54 -0
- package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
- package/src/panels/material/MaterialGraphMenu.tsx +45 -0
- package/src/panels/material/MaterialGroupNode.tsx +56 -0
- package/src/panels/material/MaterialLibrary.tsx +253 -0
- package/src/panels/material/MaterialNode.tsx +453 -0
- package/src/panels/material/MaterialNodePalette.tsx +349 -0
- package/src/panels/material/MaterialParameterControls.tsx +525 -0
- package/src/panels/material/MaterialRerouteNode.tsx +42 -0
- package/src/panels/material/MaterialWorkspace.tsx +501 -0
- package/src/panels/material/NodePreview.tsx +70 -0
- package/src/panels/material/flowModel.ts +441 -0
- package/src/panels/material/parameterValues.ts +231 -0
- package/src/panels/material/previewScheduler.ts +235 -0
- package/src/panels/material/reactFlow.css +6 -0
- package/src/panels/material/socketGlyph.ts +85 -0
- package/src/panels/material/viewportMemory.ts +58 -0
- package/src/panels/preferences/PreferencesDialog.tsx +215 -0
- package/src/panels/share/ShareComponentDialog.tsx +247 -0
- package/src/panels/stage/StageMenu.tsx +35 -5
- package/src/panels/stage/StagePanel.tsx +338 -23
- package/src/panels/stage/cellSlots.tsx +70 -0
- package/src/panels/storyboard/BoardCanvas.tsx +739 -0
- package/src/panels/storyboard/BoardCursors.tsx +158 -0
- package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
- package/src/panels/storyboard/BoardPlan.tsx +42 -0
- package/src/panels/storyboard/ComponentSheet.tsx +187 -0
- package/src/panels/storyboard/ComponentStage.tsx +235 -0
- package/src/panels/storyboard/MiniSketch.tsx +173 -0
- package/src/panels/storyboard/PartPicker.tsx +64 -0
- package/src/panels/storyboard/PlanPanel.tsx +617 -0
- package/src/panels/storyboard/ReferencePreview.tsx +217 -0
- package/src/panels/storyboard/ShotBrief.tsx +360 -0
- package/src/panels/storyboard/ShotCard.tsx +251 -0
- package/src/panels/storyboard/StagePreview.tsx +348 -0
- package/src/panels/storyboard/StillThumbs.tsx +162 -0
- package/src/panels/storyboard/StoryboardView.tsx +79 -0
- package/src/panels/storyboard/board.css +1065 -0
- package/src/panels/storyboard/board.ts +12 -0
- package/src/panels/storyboard/boardClipboard.ts +111 -0
- package/src/panels/storyboard/boardOrder.ts +68 -0
- package/src/panels/storyboard/boardRows.tsx +425 -0
- package/src/panels/storyboard/liveCap.ts +90 -0
- package/src/panels/storyboard/plan.css +295 -0
- package/src/panels/storyboard/posterCache.ts +133 -0
- package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
- package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
- package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
- package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
- package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
- package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
- package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
- package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
- package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
- package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
- package/src/panels/storyboard/shotboard/sketch.css +1392 -0
- package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
- package/src/panels/storyboard/storyboardDrop.ts +164 -0
- package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
- package/src/panels/studio/ComponentSidePanel.tsx +355 -0
- package/src/panels/studio/ComponentStudio.tsx +243 -0
- package/src/panels/studio/ComponentsHome.tsx +318 -0
- package/src/panels/studio/ComponentsPreview.tsx +179 -0
- package/src/panels/studio/EveryUse.tsx +244 -0
- package/src/panels/studio/ThisUsePanel.tsx +679 -0
- package/src/panels/studio/componentParts.tsx +341 -0
- package/src/panels/studio/components.ts +6 -0
- package/src/panels/studio/newCodeComponent.ts +66 -0
- package/src/panels/timeline/AudioFxButton.tsx +290 -0
- package/src/panels/timeline/AudioLane.tsx +674 -85
- package/src/panels/timeline/BlockRow.tsx +154 -85
- package/src/panels/timeline/ChannelRow.tsx +378 -129
- package/src/panels/timeline/CurvePane.tsx +508 -0
- package/src/panels/timeline/Filmstrip.tsx +361 -0
- package/src/panels/timeline/GenerateDrawer.tsx +407 -0
- package/src/panels/timeline/KeyStretch.tsx +115 -0
- package/src/panels/timeline/LifespanRow.tsx +730 -47
- package/src/panels/timeline/MarkerLane.tsx +46 -8
- package/src/panels/timeline/PlayButton.tsx +91 -0
- package/src/panels/timeline/PropertyPicker.tsx +254 -0
- package/src/panels/timeline/ShotRow.tsx +400 -0
- package/src/panels/timeline/SoundMode.tsx +1406 -0
- package/src/panels/timeline/TimelineDock.tsx +2338 -272
- package/src/panels/timeline/TransportVolume.tsx +163 -0
- package/src/panels/timeline/beatRuler.ts +74 -0
- package/src/panels/timeline/curveModel.ts +500 -0
- package/src/panels/timeline/duckUnderVoice.ts +68 -0
- package/src/panels/timeline/keyframeMenu.ts +138 -0
- package/src/panels/timeline/labelWidth.ts +49 -0
- package/src/panels/timeline/marquee.ts +155 -0
- package/src/panels/timeline/model.ts +1860 -199
- package/src/panels/timeline/snap.ts +125 -43
- package/src/panels/timeline/staggerPreview.tsx +92 -0
- package/src/panels/timeline/takeMeasure.ts +72 -0
- package/src/panels/timeline/useLive.ts +48 -0
- package/src/panels/timeline/viewport.ts +234 -0
- package/src/panels/toolbar/BackChip.tsx +116 -0
- package/src/panels/toolbar/CameraViewBar.tsx +118 -49
- package/src/panels/toolbar/CanvasBar.tsx +117 -0
- package/src/panels/toolbar/InsertButton.tsx +38 -0
- package/src/panels/toolbar/IsolationTrail.tsx +156 -0
- package/src/panels/toolbar/PanelToggles.tsx +86 -0
- package/src/panels/toolbar/ShadingBar.tsx +119 -26
- package/src/panels/toolbar/ToolDockBar.tsx +329 -80
- package/src/panels/toolbar/ToolFooter.tsx +102 -0
- package/src/panels/toolbar/ViewModeBar.tsx +247 -38
- package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
- package/src/panels/toolbar/useAddRows.tsx +326 -0
- package/src/panels/toolbar/useDismiss.ts +122 -0
- package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
- package/src/panels/versions/versionModel.ts +103 -0
- package/src/panels/virtualWindow.ts +271 -0
- package/src/shotTokens.css +151 -60
- package/src/stage/FrustumGizmo.tsx +504 -84
- package/src/stage/GridPlane.tsx +225 -16
- package/src/stage/NodePreviews.tsx +628 -0
- package/src/stage/PerfHud.tsx +437 -47
- package/src/stage/SplitRenderer.tsx +816 -169
- package/src/stage/StageApp.tsx +4274 -486
- package/src/stage/StageRootBoundary.tsx +114 -0
- package/src/stage/StageTools.tsx +651 -151
- package/src/stage/TransformGizmo.tsx +696 -0
- package/src/stage/adaptivePresentation.ts +145 -0
- package/src/stage/bridgeClient.ts +2522 -41
- package/src/stage/exportWorker.ts +9 -0
- package/src/stage/feed.ts +305 -14
- package/src/stage/fontPreview.ts +176 -0
- package/src/stage/frustumGeometry.ts +319 -1
- package/src/stage/furnitureTokens.ts +33 -0
- package/src/stage/gizmoMath.ts +508 -16
- package/src/stage/gpuTimer.ts +276 -0
- package/src/stage/instanceModules.ts +194 -33
- package/src/stage/interactions/move.ts +24 -5
- package/src/stage/isolation.ts +291 -33
- package/src/stage/jsSelfProfiler.ts +325 -0
- package/src/stage/main.tsx +116 -16
- package/src/stage/modalState.ts +222 -23
- package/src/stage/moduleEpoch.ts +60 -0
- package/src/stage/motionPathStage.ts +279 -0
- package/src/stage/motionPreview.ts +74 -0
- package/src/stage/overlays/Overlays.tsx +1112 -159
- package/src/stage/overlays/Rulers.tsx +495 -0
- package/src/stage/panes.ts +159 -10
- package/src/stage/project.ts +68 -3
- package/src/stage/renderScaleController.ts +730 -0
- package/src/stage/rulers.ts +129 -0
- package/src/stage/sceneExtent.ts +75 -0
- package/src/stage/shading.ts +20 -0
- package/src/stage/splineStage.ts +632 -0
- package/src/stage/stagePerformanceTelemetry.ts +717 -0
- package/src/stage/stageTheme.ts +36 -0
- package/src/stage/stageWarmup.ts +11 -0
- package/src/stage/textAutoSize.ts +124 -0
- package/src/stage/tier1Export.ts +641 -0
- package/src/stage/tokenColor.ts +14 -9
- package/src/stage/view.ts +56 -9
- package/src/stage/workingView.ts +277 -33
- package/src/state/accountPrefs.ts +261 -0
- package/src/state/agentActions.ts +173 -15
- package/src/state/agentActivity.ts +198 -0
- package/src/state/agentQuestion.ts +239 -0
- package/src/state/bootOpen.ts +42 -0
- package/src/state/cameraVerbs.ts +139 -0
- package/src/state/clientId.ts +6 -1
- package/src/state/comments.ts +1099 -0
- package/src/state/context.tsx +19 -1
- package/src/state/editorPrefs.ts +220 -0
- package/src/state/errorReports.ts +121 -0
- package/src/state/fieldWrite.ts +161 -0
- package/src/state/fontRecents.ts +70 -0
- package/src/state/fonts.ts +656 -0
- package/src/state/graphClipboard.ts +414 -0
- package/src/state/imageThumbs.ts +216 -0
- package/src/state/insert.ts +444 -67
- package/src/state/isolationTrail.ts +92 -0
- package/src/state/keyMoves.ts +134 -0
- package/src/state/keyframeClipboard.ts +177 -0
- package/src/state/keymap.ts +1153 -117
- package/src/state/layoutPrefs.ts +272 -0
- package/src/state/layouts.ts +205 -0
- package/src/state/measureMedia.ts +167 -0
- package/src/state/menu.ts +2341 -28
- package/src/state/motionStamp.ts +7 -0
- package/src/state/panelSize.ts +375 -5
- package/src/state/pendingChanges.ts +119 -0
- package/src/state/playHold.ts +78 -0
- package/src/state/playbackPreparation.ts +562 -0
- package/src/state/posterCapture.ts +242 -0
- package/src/state/presence.ts +254 -0
- package/src/state/projectComponents.ts +160 -0
- package/src/state/renderEvents.ts +298 -18
- package/src/state/runtimeErrors.ts +46 -0
- package/src/state/selection.ts +12 -0
- package/src/state/shortcuts.ts +1606 -0
- package/src/state/sketchGuide.ts +51 -0
- package/src/state/splineRefs.ts +87 -0
- package/src/state/stageHealth.ts +259 -0
- package/src/state/stageWatchdog.ts +47 -0
- package/src/state/stillStage.ts +330 -0
- package/src/state/stillThumbs.ts +474 -0
- package/src/state/store.ts +23591 -2982
- package/src/state/storyboardModel.ts +528 -0
- package/src/state/sync.ts +1257 -0
- package/src/state/syncCache.ts +159 -0
- package/src/state/triangles.ts +13 -0
- package/src/state/types.ts +1698 -55
- package/src/state/unsavedGuard.ts +49 -0
- package/src/state/viewMode.ts +200 -0
- package/src/state/webmcp.ts +1128 -0
- package/src/state/workspace.ts +1147 -0
- package/src/styles/assets-tab.css +304 -0
- package/src/styles/canvas.css +545 -0
- package/src/styles/comments.css +1435 -0
- package/src/styles/controls.css +162 -0
- package/src/styles/dialogs.css +390 -0
- package/src/styles/insert-drawer.css +586 -0
- package/src/styles/inspector-board.css +207 -0
- package/src/styles/inspector-object.css +486 -0
- package/src/styles/inspector-shot.css +252 -0
- package/src/styles/inspector.css +360 -0
- package/src/styles/left-column.css +1628 -0
- package/src/styles/library-preview.css +575 -0
- package/src/styles/library.css +512 -0
- package/src/styles/modes/code.css +569 -0
- package/src/styles/modes/component.css +1561 -0
- package/src/styles/modes/material.css +1201 -0
- package/src/styles/modes/storyboard.css +518 -0
- package/src/styles/share-component.css +49 -0
- package/src/styles/shell.css +653 -0
- package/src/styles/shot-brief.css +360 -0
- package/src/styles/states.css +76 -0
- package/src/styles/sync.css +380 -0
- package/src/styles/timeline-graph.css +384 -0
- package/src/styles/timeline-meters.css +89 -0
- package/src/styles/timeline-rows.css +1959 -0
- package/src/styles/timeline-sound.css +1003 -0
- package/src/styles/timeline.css +754 -0
- package/src/styles/tool-footer.css +525 -0
- package/src/styles/topbar.css +886 -0
- package/src/styles/versions.css +262 -0
- package/src/styles.css +72 -2921
- package/src/tools.ts +108 -23
- package/dist/assets/index-B7notge7.css +0 -1
- package/dist/assets/index-BBE6Ta9L.js +0 -52
- package/src/panels/files/icons.tsx +0 -118
- package/src/panels/header/DocSwitcher.tsx +0 -85
- package/src/panels/header/TransportBar.tsx +0 -86
- package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
- package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
- package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
- package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
- package/src/panels/inspector/animate/useBlocks.ts +0 -43
- package/src/panels/material/MaterialPicker.tsx +0 -172
- package/src/panels/timeline/SceneStrip.tsx +0 -145
- package/src/panels/toolbar/AddMenu.tsx +0 -230
- package/src/stage/MoveGizmo.tsx +0 -411
|
@@ -0,0 +1,717 @@
|
|
|
1
|
+
// Correlated stage frame telemetry and the one-second window sampler behind
|
|
2
|
+
// the editor's fps readout and the playback gate.
|
|
3
|
+
//
|
|
4
|
+
// The minimal sampler is product-always WHILE PLAYING: one timestamp when a
|
|
5
|
+
// frame is requested and one when the runtime confirms that revision was
|
|
6
|
+
// presented. Those readings used to control backing-store DPR; the ladder is
|
|
7
|
+
// gone (adaptivePresentation.ts), so a closed window is reported, with the
|
|
8
|
+
// GPU frame times that landed in it (`noteGpu`). Each presented frame of the
|
|
9
|
+
// transport goes to the playback render scale (`subscribePresented`), whose
|
|
10
|
+
// deadlines it is (renderScaleController.ts), timed by its request; its
|
|
11
|
+
// windows are its own, and this one's rate is a readout.
|
|
12
|
+
// Rich history is opt-in under `?perf=1`; when it is off, paused scrubs and
|
|
13
|
+
// incidental redraws perform no telemetry-only wall-clock reads or
|
|
14
|
+
// ring-buffer work.
|
|
15
|
+
import { ADAPTIVE_WINDOW_MS } from "./adaptivePresentation.js";
|
|
16
|
+
import type { AdaptivePresentationLevel, PresentationQuality } from "./adaptivePresentation.js";
|
|
17
|
+
import { canonicalJSON } from "@dailies-studio/contract";
|
|
18
|
+
import { DEFAULT_SHOT_FPS } from "@dailies-studio/shot/doc";
|
|
19
|
+
import type { Doc3D } from "@dailies-studio/shot/doc";
|
|
20
|
+
|
|
21
|
+
export interface StageFrameRequest {
|
|
22
|
+
requestSequence: number;
|
|
23
|
+
frame: number;
|
|
24
|
+
requestedAt: number | null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface PublishedFrame extends StageFrameRequest {
|
|
28
|
+
publicationToken: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface StagePresentedFrameLike {
|
|
32
|
+
revision: number;
|
|
33
|
+
frame: number;
|
|
34
|
+
/** Editor publication identity echoed by ShotRuntime. */
|
|
35
|
+
presentationToken?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface StagePerformanceSnapshot {
|
|
39
|
+
requestedFrames: number;
|
|
40
|
+
publishedFrames: number;
|
|
41
|
+
presentedFrames: number;
|
|
42
|
+
matchedPresentations: number;
|
|
43
|
+
unmatchedPresentations: number;
|
|
44
|
+
droppedPublications: number;
|
|
45
|
+
requestCoalesced: number;
|
|
46
|
+
publicationCoalesced: number;
|
|
47
|
+
latencySamples: number;
|
|
48
|
+
latencyP50Ms: number | null;
|
|
49
|
+
latencyP95Ms: number | null;
|
|
50
|
+
latencyP99Ms: number | null;
|
|
51
|
+
lastWindowP95Ms: number | null;
|
|
52
|
+
lastWindowPresentedFrames: number;
|
|
53
|
+
lastWindowExpectedFrames: number;
|
|
54
|
+
lastWindowDroppedPublications: number;
|
|
55
|
+
lastPresentedRevision: number;
|
|
56
|
+
qualityLevel: AdaptivePresentationLevel;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** One closed adaptive window: what the stage actually put on screen against
|
|
60
|
+
* what the document's cadence asked of it. `durationMs` is carried rather
|
|
61
|
+
* than assumed so a reader can turn frames into a rate without importing
|
|
62
|
+
* `ADAPTIVE_WINDOW_MS`. */
|
|
63
|
+
export interface StagePerformanceWindow {
|
|
64
|
+
presentedFrames: number;
|
|
65
|
+
expectedFrames: number;
|
|
66
|
+
droppedPublications: number;
|
|
67
|
+
durationMs: number;
|
|
68
|
+
/** The p95 of the GPU frame times that landed in the window (`noteGpu`),
|
|
69
|
+
* or null when none did. */
|
|
70
|
+
gpuP95Ms: number | null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface StagePerformanceInterval {
|
|
74
|
+
requestedFrames: number;
|
|
75
|
+
publishedFrames: number;
|
|
76
|
+
presentedFrames: number;
|
|
77
|
+
matchedPresentations: number;
|
|
78
|
+
unmatchedPresentations: number;
|
|
79
|
+
droppedPublications: number;
|
|
80
|
+
requestCoalesced: number;
|
|
81
|
+
publicationCoalesced: number;
|
|
82
|
+
renderPasses: number;
|
|
83
|
+
effectivePasses: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface StagePresentationSample {
|
|
87
|
+
sequence: number;
|
|
88
|
+
publicationToken: number;
|
|
89
|
+
requestSequence: number;
|
|
90
|
+
frame: number;
|
|
91
|
+
revision: number;
|
|
92
|
+
requestedAt: number;
|
|
93
|
+
presentedAt: number;
|
|
94
|
+
latencyMs: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface StageDebugDocumentIdentity {
|
|
98
|
+
id: string;
|
|
99
|
+
name: string;
|
|
100
|
+
title: string | null;
|
|
101
|
+
size: [number, number];
|
|
102
|
+
fps: [number, number];
|
|
103
|
+
scenes: Array<[string, number, number]>;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface StageDebugDocumentPresentation {
|
|
107
|
+
frame: number;
|
|
108
|
+
revision: number;
|
|
109
|
+
presentedAt: number;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The document that crossed the stage bridge, plus proof that this exact
|
|
114
|
+
* object reached ShotRuntime's presentation callback.
|
|
115
|
+
*
|
|
116
|
+
* Object identity stays stage-local. Only the compact structural identity is
|
|
117
|
+
* published, so a diagnostics poll never serializes the authored document or
|
|
118
|
+
* puts its size on the measured frame path.
|
|
119
|
+
*/
|
|
120
|
+
export interface StageDebugDocumentSnapshot {
|
|
121
|
+
identity: StageDebugDocumentIdentity;
|
|
122
|
+
fingerprint: string | null;
|
|
123
|
+
fingerprintError: string | null;
|
|
124
|
+
loadSequence: number;
|
|
125
|
+
loadedAtSampleSequence: number;
|
|
126
|
+
presentationCount: number;
|
|
127
|
+
lastPresentation: StageDebugDocumentPresentation | null;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const DEBUG_DOCUMENT_FINGERPRINT_PREFIX = "sha256-canonical-json-v1:";
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Whole-value document fingerprint shared by the stage and browser gate.
|
|
134
|
+
*
|
|
135
|
+
* `canonicalJSON` sorts object keys and preserves array order, so equivalent
|
|
136
|
+
* values do not depend on insertion order while every authored array remains
|
|
137
|
+
* order-sensitive. SHA-256 makes accidental acceptance of different values
|
|
138
|
+
* computationally negligible. This is an audit identity, not an authenticity
|
|
139
|
+
* claim: both sides run in the same local process and no secret is involved.
|
|
140
|
+
*/
|
|
141
|
+
export async function debugDocumentFingerprint(doc: Doc3D): Promise<string> {
|
|
142
|
+
if (globalThis.crypto?.subtle === undefined) {
|
|
143
|
+
throw new Error("Web Crypto SHA-256 is unavailable");
|
|
144
|
+
}
|
|
145
|
+
const bytes = new TextEncoder().encode(canonicalJSON(doc));
|
|
146
|
+
const digest = new Uint8Array(await globalThis.crypto.subtle.digest("SHA-256", bytes));
|
|
147
|
+
return (
|
|
148
|
+
DEBUG_DOCUMENT_FINGERPRINT_PREFIX +
|
|
149
|
+
Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("")
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Read-only opt-in query used by the browser benchmark. */
|
|
154
|
+
export interface StagePerformanceDebugSnapshot {
|
|
155
|
+
counters: StagePerformanceSnapshot;
|
|
156
|
+
samples: readonly StagePresentationSample[];
|
|
157
|
+
latestSampleSequence: number;
|
|
158
|
+
/** First row retained. A cursor is stale when `cursor + 1` is below it. */
|
|
159
|
+
oldestSampleSequence: number;
|
|
160
|
+
/** Present only in rich diagnostics, and only while a document is loaded. */
|
|
161
|
+
document: StageDebugDocumentSnapshot | null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export interface StagePerformanceTelemetryOptions {
|
|
165
|
+
rich?: boolean;
|
|
166
|
+
now?: () => number;
|
|
167
|
+
richLatencyCapacity?: number;
|
|
168
|
+
/** Whether the stage is on screen at all. A hidden tab has its rAF throttled
|
|
169
|
+
* to seconds apart, so the transport requests almost nothing and the window
|
|
170
|
+
* reads as a scene in deep trouble — which it is not. Defaults to the
|
|
171
|
+
* document's own visibility; a test passes its own. */
|
|
172
|
+
visible?: () => boolean;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const DEFAULT_RICH_LATENCY_CAPACITY = 512;
|
|
176
|
+
const MAX_PENDING_PUBLICATIONS = 512;
|
|
177
|
+
|
|
178
|
+
export class StagePerformanceTelemetry {
|
|
179
|
+
readonly #now: () => number;
|
|
180
|
+
readonly #visible: () => boolean;
|
|
181
|
+
readonly #richLatencyCapacity: number;
|
|
182
|
+
readonly #qualityListeners = new Set<() => void>();
|
|
183
|
+
/** Told once per CLOSED window, with what that window actually held. The
|
|
184
|
+
* quality listeners above fire only when the policy changes rung, which is
|
|
185
|
+
* rare by design; a readout of the current rate needs every window. */
|
|
186
|
+
readonly #windowListeners = new Set<(window: StagePerformanceWindow) => void>();
|
|
187
|
+
/** Told of every frame of the transport that reached the canvas, with the
|
|
188
|
+
* clock read its request took and its frame (`subscribePresented`). */
|
|
189
|
+
readonly #presentedListeners = new Set<(requestedAt: number, frame: number) => void>();
|
|
190
|
+
readonly #debugSampleListeners = new Set<(sample: StagePresentationSample) => void>();
|
|
191
|
+
readonly #pendingByToken = new Map<number, PublishedFrame>();
|
|
192
|
+
readonly #windowLatencies: number[] = [];
|
|
193
|
+
readonly #windowGpu: number[] = [];
|
|
194
|
+
readonly #richSamples: StagePresentationSample[] = [];
|
|
195
|
+
|
|
196
|
+
#rich: boolean;
|
|
197
|
+
#debugDocumentSource: Doc3D | null = null;
|
|
198
|
+
#debugDocument: StageDebugDocumentSnapshot | null = null;
|
|
199
|
+
#debugDocumentLoadSequence = 0;
|
|
200
|
+
#playing = false;
|
|
201
|
+
// The lane's own default rate until `setFrameRate` hears from a document,
|
|
202
|
+
// with the period derived exactly as that setter derives it.
|
|
203
|
+
#fpsNumerator = DEFAULT_SHOT_FPS.num;
|
|
204
|
+
#fpsDenominator = DEFAULT_SHOT_FPS.den;
|
|
205
|
+
#expectedFrameRemainder = 0;
|
|
206
|
+
#windowStartedAt = 0;
|
|
207
|
+
#requestSequence = 0;
|
|
208
|
+
#publishSequence = 0;
|
|
209
|
+
#sampleSequence = 0;
|
|
210
|
+
#lastPresentedRevision = 0;
|
|
211
|
+
#requestedFrames = 0;
|
|
212
|
+
#publishedFrames = 0;
|
|
213
|
+
#presentedFrames = 0;
|
|
214
|
+
#matchedPresentations = 0;
|
|
215
|
+
#unmatchedPresentations = 0;
|
|
216
|
+
#droppedPublications = 0;
|
|
217
|
+
#requestCoalesced = 0;
|
|
218
|
+
#publicationCoalesced = 0;
|
|
219
|
+
#lastWindowP95Ms: number | null = null;
|
|
220
|
+
#windowPresentedFrames = 0;
|
|
221
|
+
#windowDroppedPublications = 0;
|
|
222
|
+
#lastWindowPresentedFrames = 0;
|
|
223
|
+
#lastWindowExpectedFrames = 0;
|
|
224
|
+
#lastWindowDroppedPublications = 0;
|
|
225
|
+
|
|
226
|
+
constructor(options: StagePerformanceTelemetryOptions = {}) {
|
|
227
|
+
this.#rich = options.rich === true;
|
|
228
|
+
this.#now = options.now ?? (() => performance.now());
|
|
229
|
+
this.#visible =
|
|
230
|
+
options.visible ??
|
|
231
|
+
(() => typeof document === "undefined" || document.visibilityState === "visible");
|
|
232
|
+
const requestedCapacity = options.richLatencyCapacity ?? DEFAULT_RICH_LATENCY_CAPACITY;
|
|
233
|
+
this.#richLatencyCapacity = Number.isFinite(requestedCapacity)
|
|
234
|
+
? Math.max(1, Math.round(requestedCapacity))
|
|
235
|
+
: DEFAULT_RICH_LATENCY_CAPACITY;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
setRichEnabled(enabled: boolean): void {
|
|
239
|
+
this.#rich = enabled;
|
|
240
|
+
if (enabled) return;
|
|
241
|
+
this.#richSamples.length = 0;
|
|
242
|
+
this.#debugSampleListeners.clear();
|
|
243
|
+
this.#debugDocumentSource = null;
|
|
244
|
+
this.#debugDocument = null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Records the bridge's accepted document for local rich diagnostics.
|
|
249
|
+
*
|
|
250
|
+
* The source reference never leaves this class. `present` receives the
|
|
251
|
+
* document rendered by StageApp and only confirms a presentation when the
|
|
252
|
+
* references are identical. A late callback from a replaced document,
|
|
253
|
+
* including one that reused the same persisted id, therefore cannot satisfy
|
|
254
|
+
* the browser gate.
|
|
255
|
+
*/
|
|
256
|
+
setDebugDocument(doc: Doc3D | null): void {
|
|
257
|
+
if (!this.#rich || this.#debugDocumentSource === doc) return;
|
|
258
|
+
this.#debugDocumentSource = doc;
|
|
259
|
+
if (doc === null) {
|
|
260
|
+
this.#debugDocument = null;
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
this.#debugDocumentLoadSequence += 1;
|
|
264
|
+
const debugDocument: StageDebugDocumentSnapshot = {
|
|
265
|
+
identity: {
|
|
266
|
+
id: doc.id,
|
|
267
|
+
name: doc.name,
|
|
268
|
+
title: doc.title ?? null,
|
|
269
|
+
size: [doc.size.width, doc.size.height],
|
|
270
|
+
fps: [doc.fps.num, doc.fps.den],
|
|
271
|
+
scenes: doc.scenes.map((id) => {
|
|
272
|
+
const scene = doc.scenesById[id]!;
|
|
273
|
+
return [id, scene.durationFrames, Object.keys(scene.layersById).length];
|
|
274
|
+
}),
|
|
275
|
+
},
|
|
276
|
+
fingerprint: null,
|
|
277
|
+
fingerprintError: null,
|
|
278
|
+
loadSequence: this.#debugDocumentLoadSequence,
|
|
279
|
+
loadedAtSampleSequence: this.#sampleSequence,
|
|
280
|
+
presentationCount: 0,
|
|
281
|
+
lastPresentation: null,
|
|
282
|
+
};
|
|
283
|
+
this.#debugDocument = debugDocument;
|
|
284
|
+
// The canonical walk and digest exist only under rich diagnostics. The
|
|
285
|
+
// generation checks keep a late digest from labelling a replacement doc.
|
|
286
|
+
void debugDocumentFingerprint(doc).then(
|
|
287
|
+
(fingerprint) => {
|
|
288
|
+
if (this.#debugDocumentSource !== doc || this.#debugDocument !== debugDocument) return;
|
|
289
|
+
debugDocument.fingerprint = fingerprint;
|
|
290
|
+
},
|
|
291
|
+
(error: unknown) => {
|
|
292
|
+
if (this.#debugDocumentSource !== doc || this.#debugDocument !== debugDocument) return;
|
|
293
|
+
debugDocument.fingerprintError = error instanceof Error ? error.message : String(error);
|
|
294
|
+
},
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
setFrameRate(fps: { num: number; den: number } | null): void {
|
|
299
|
+
if (
|
|
300
|
+
fps === null ||
|
|
301
|
+
!Number.isFinite(fps.num) ||
|
|
302
|
+
!Number.isFinite(fps.den) ||
|
|
303
|
+
fps.num <= 0 ||
|
|
304
|
+
fps.den <= 0
|
|
305
|
+
) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
this.#fpsNumerator = fps.num;
|
|
309
|
+
this.#fpsDenominator = fps.den;
|
|
310
|
+
this.#expectedFrameRemainder = 0;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** A document/topology replacement invalidates every outstanding token. */
|
|
314
|
+
resetPublications(): void {
|
|
315
|
+
this.#pendingByToken.clear();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
setPlaying(playing: boolean): void {
|
|
319
|
+
if (playing === this.#playing) return;
|
|
320
|
+
this.#playing = playing;
|
|
321
|
+
this.#windowLatencies.length = 0;
|
|
322
|
+
this.#windowGpu.length = 0;
|
|
323
|
+
this.#expectedFrameRemainder = 0;
|
|
324
|
+
this.#lastWindowP95Ms = null;
|
|
325
|
+
this.#pendingByToken.clear();
|
|
326
|
+
this.#windowPresentedFrames = 0;
|
|
327
|
+
this.#windowDroppedPublications = 0;
|
|
328
|
+
this.#lastWindowPresentedFrames = 0;
|
|
329
|
+
this.#lastWindowExpectedFrames = 0;
|
|
330
|
+
this.#lastWindowDroppedPublications = 0;
|
|
331
|
+
if (playing) {
|
|
332
|
+
this.#windowStartedAt = this.#now();
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
this.#windowStartedAt = 0;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
request(frame: number): StageFrameRequest | null {
|
|
339
|
+
// A paused stage without the opt-in diagnostics has no latency policy to
|
|
340
|
+
// drive. Return the null token before allocating, reading the clock, or
|
|
341
|
+
// touching diagnostic counters.
|
|
342
|
+
if (!this.#playing && !this.#rich) return null;
|
|
343
|
+
const requestedAt = this.#now();
|
|
344
|
+
if (this.#playing) this.#rollWindowsBefore(requestedAt);
|
|
345
|
+
this.#requestedFrames += 1;
|
|
346
|
+
this.#requestSequence += 1;
|
|
347
|
+
return { requestSequence: this.#requestSequence, frame, requestedAt };
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
coalesceRequest(request: StageFrameRequest | null): void {
|
|
351
|
+
if (request === null) return;
|
|
352
|
+
this.#requestCoalesced += 1;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
publish(request: StageFrameRequest | null): number | null {
|
|
356
|
+
if (request === null) return null;
|
|
357
|
+
this.#publishedFrames += 1;
|
|
358
|
+
this.#publishSequence += 1;
|
|
359
|
+
const publicationToken = this.#publishSequence;
|
|
360
|
+
this.#pendingByToken.set(publicationToken, { ...request, publicationToken });
|
|
361
|
+
if (this.#pendingByToken.size > MAX_PENDING_PUBLICATIONS) {
|
|
362
|
+
const oldest = this.#pendingByToken.keys().next().value as number | undefined;
|
|
363
|
+
if (oldest !== undefined) {
|
|
364
|
+
this.#pendingByToken.delete(oldest);
|
|
365
|
+
this.#publicationCoalesced += 1;
|
|
366
|
+
this.#droppedPublications += 1;
|
|
367
|
+
if (this.#playing) this.#windowDroppedPublications += 1;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
return publicationToken;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
present(info: StagePresentedFrameLike, document: Doc3D | null = null): void {
|
|
374
|
+
if (!this.#playing && !this.#rich) return;
|
|
375
|
+
const presentedAt = this.#now();
|
|
376
|
+
if (
|
|
377
|
+
this.#rich &&
|
|
378
|
+
document !== null &&
|
|
379
|
+
document === this.#debugDocumentSource &&
|
|
380
|
+
this.#debugDocument !== null
|
|
381
|
+
) {
|
|
382
|
+
this.#debugDocument.presentationCount += 1;
|
|
383
|
+
this.#debugDocument.lastPresentation = {
|
|
384
|
+
frame: info.frame,
|
|
385
|
+
revision: info.revision,
|
|
386
|
+
presentedAt,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
if (this.#playing) this.#rollWindowsBefore(presentedAt);
|
|
390
|
+
this.#presentedFrames += 1;
|
|
391
|
+
this.#lastPresentedRevision = Math.max(this.#lastPresentedRevision, info.revision);
|
|
392
|
+
const token = info.presentationToken;
|
|
393
|
+
const pending = token === undefined ? undefined : this.#pendingByToken.get(token);
|
|
394
|
+
if (pending === undefined || pending.frame !== info.frame) {
|
|
395
|
+
this.#unmatchedPresentations += 1;
|
|
396
|
+
if (this.#playing) this.#rollWindowsThrough(presentedAt);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Any older pending publication was superseded before it reached the
|
|
401
|
+
// canvas. Identity is the publication token, never the authored frame: a
|
|
402
|
+
// seek away and back may publish the same frame several times.
|
|
403
|
+
for (const [pendingToken, row] of this.#pendingByToken) {
|
|
404
|
+
if (row.publicationToken < pending.publicationToken) {
|
|
405
|
+
this.#publicationCoalesced += 1;
|
|
406
|
+
this.#droppedPublications += 1;
|
|
407
|
+
if (this.#playing) this.#windowDroppedPublications += 1;
|
|
408
|
+
}
|
|
409
|
+
if (row.publicationToken <= pending.publicationToken) this.#pendingByToken.delete(pendingToken);
|
|
410
|
+
}
|
|
411
|
+
this.#matchedPresentations += 1;
|
|
412
|
+
if (this.#playing) {
|
|
413
|
+
this.#windowPresentedFrames += 1;
|
|
414
|
+
this.#notifyPresented(pending.requestedAt ?? presentedAt, pending.frame);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (pending.requestedAt === null) {
|
|
418
|
+
if (this.#playing) this.#rollWindowsThrough(presentedAt);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
const latency = Math.max(0, presentedAt - pending.requestedAt);
|
|
422
|
+
if (this.#rich) {
|
|
423
|
+
this.#pushRichSample({
|
|
424
|
+
publicationToken: pending.publicationToken,
|
|
425
|
+
requestSequence: pending.requestSequence,
|
|
426
|
+
frame: pending.frame,
|
|
427
|
+
revision: info.revision,
|
|
428
|
+
requestedAt: pending.requestedAt,
|
|
429
|
+
presentedAt,
|
|
430
|
+
latencyMs: latency,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
if (!this.#playing) return;
|
|
434
|
+
this.#windowLatencies.push(latency);
|
|
435
|
+
this.#rollWindowsThrough(presentedAt);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/** One frame's GPU time, ms, as the stage's `GpuTimer` resolved it: into
|
|
439
|
+
* the open window while playing, for the report's `gpuMs`. A paused
|
|
440
|
+
* stage's draws are dropped, as its presentations are. */
|
|
441
|
+
noteGpu(ms: number): void {
|
|
442
|
+
if (this.#playing && Number.isFinite(ms)) this.#windowGpu.push(ms);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** Always 0. The DPR ladder is gone; the method survives for the HUD, the
|
|
446
|
+
* gate and the counters that print it. The playback render scale is not a
|
|
447
|
+
* rung of it: the canvas keeps its density (renderScaleController.ts). */
|
|
448
|
+
qualityLevel(): AdaptivePresentationLevel {
|
|
449
|
+
return 0;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** Never fires. Kept so a caller written against the ladder still type
|
|
453
|
+
* checks; nothing changes rung any more. */
|
|
454
|
+
subscribeQuality(listener: () => void): () => void {
|
|
455
|
+
this.#qualityListeners.add(listener);
|
|
456
|
+
return () => this.#qualityListeners.delete(listener);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Every closed, VISIBLE window: what was presented against what the cadence
|
|
461
|
+
* asked for.
|
|
462
|
+
*
|
|
463
|
+
* Windows roll only while playing (`#rollWindowsBefore`/`Through` both guard
|
|
464
|
+
* on `#playing`), so this is one call a second during playback and silent
|
|
465
|
+
* otherwise. A window nobody could see returns early above without notifying:
|
|
466
|
+
* a hidden tab presents nothing, and reporting 0 fps for it would be a
|
|
467
|
+
* measurement of the tab rather than of the scene.
|
|
468
|
+
*/
|
|
469
|
+
subscribeWindow(listener: (window: StagePerformanceWindow) => void): () => void {
|
|
470
|
+
this.#windowListeners.add(listener);
|
|
471
|
+
return () => this.#windowListeners.delete(listener);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Every frame of the transport that reached the canvas while playing, with
|
|
476
|
+
* its document frame, timed by the clock read its request took (`request`,
|
|
477
|
+
* in the transport's tick before the frame was drawn): the playback render
|
|
478
|
+
* scale's deadlines (renderScaleController.ts `presented`). Timed by the
|
|
479
|
+
* echo, after the draw in the same task, a frame drawn 10 ms slower than the
|
|
480
|
+
* one before it, in time for its refresh, followed it by more than a
|
|
481
|
+
* refresh and a half at 60 fps, late.
|
|
482
|
+
*
|
|
483
|
+
* A publication the transport made and the runtime presented, so neither a
|
|
484
|
+
* preparing pass's frames (the stage is not playing for this sampler), nor a
|
|
485
|
+
* superseded publication, nor a redraw of a frame already on screen, nor a
|
|
486
|
+
* frame the playback scale held off the canvas, which the runtime never
|
|
487
|
+
* echoes. One call per presented frame and nothing else while it plays.
|
|
488
|
+
*/
|
|
489
|
+
subscribePresented(listener: (requestedAt: number, frame: number) => void): () => void {
|
|
490
|
+
this.#presentedListeners.add(listener);
|
|
491
|
+
return () => this.#presentedListeners.delete(listener);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
snapshot(): StagePerformanceSnapshot {
|
|
495
|
+
const richLatencies = this.#richSamples.map((sample) => sample.latencyMs);
|
|
496
|
+
return {
|
|
497
|
+
requestedFrames: this.#requestedFrames,
|
|
498
|
+
publishedFrames: this.#publishedFrames,
|
|
499
|
+
presentedFrames: this.#presentedFrames,
|
|
500
|
+
matchedPresentations: this.#matchedPresentations,
|
|
501
|
+
unmatchedPresentations: this.#unmatchedPresentations,
|
|
502
|
+
droppedPublications: this.#droppedPublications,
|
|
503
|
+
requestCoalesced: this.#requestCoalesced,
|
|
504
|
+
publicationCoalesced: this.#publicationCoalesced,
|
|
505
|
+
latencySamples: this.#richSamples.length,
|
|
506
|
+
latencyP50Ms: percentile(richLatencies, 0.5),
|
|
507
|
+
latencyP95Ms: percentile(richLatencies, 0.95),
|
|
508
|
+
latencyP99Ms: percentile(richLatencies, 0.99),
|
|
509
|
+
lastWindowP95Ms: this.#lastWindowP95Ms,
|
|
510
|
+
lastWindowPresentedFrames: this.#lastWindowPresentedFrames,
|
|
511
|
+
lastWindowExpectedFrames: this.#lastWindowExpectedFrames,
|
|
512
|
+
lastWindowDroppedPublications: this.#lastWindowDroppedPublications,
|
|
513
|
+
lastPresentedRevision: this.#lastPresentedRevision,
|
|
514
|
+
qualityLevel: 0,
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Exact samples after a caller-owned cursor. Null while rich diagnostics are
|
|
520
|
+
* disabled, so production callers cannot accidentally turn the ring on by
|
|
521
|
+
* reading it.
|
|
522
|
+
*/
|
|
523
|
+
debugSnapshot(afterSequence = 0): StagePerformanceDebugSnapshot | null {
|
|
524
|
+
if (!this.#rich) return null;
|
|
525
|
+
const cursor = Number.isFinite(afterSequence) ? Math.max(0, Math.floor(afterSequence)) : 0;
|
|
526
|
+
const oldestSampleSequence = this.#richSamples[0]?.sequence ?? this.#sampleSequence + 1;
|
|
527
|
+
return {
|
|
528
|
+
counters: this.snapshot(),
|
|
529
|
+
samples: this.#richSamples
|
|
530
|
+
.filter((sample) => sample.sequence > cursor)
|
|
531
|
+
.map((sample) => ({ ...sample })),
|
|
532
|
+
latestSampleSequence: this.#sampleSequence,
|
|
533
|
+
oldestSampleSequence,
|
|
534
|
+
document:
|
|
535
|
+
this.#debugDocument === null
|
|
536
|
+
? null
|
|
537
|
+
: {
|
|
538
|
+
...this.#debugDocument,
|
|
539
|
+
identity: {
|
|
540
|
+
...this.#debugDocument.identity,
|
|
541
|
+
size: [...this.#debugDocument.identity.size],
|
|
542
|
+
fps: [...this.#debugDocument.identity.fps],
|
|
543
|
+
scenes: this.#debugDocument.identity.scenes.map((scene) => [...scene]),
|
|
544
|
+
},
|
|
545
|
+
lastPresentation:
|
|
546
|
+
this.#debugDocument.lastPresentation === null
|
|
547
|
+
? null
|
|
548
|
+
: { ...this.#debugDocument.lastPresentation },
|
|
549
|
+
},
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* O(1) cursor for callers that only need to ignore presentations that
|
|
555
|
+
* happened before they subscribed. Null keeps the rich-mode boundary
|
|
556
|
+
* explicit without copying or walking the retained sample ring.
|
|
557
|
+
*/
|
|
558
|
+
debugSampleSequence(): number | null {
|
|
559
|
+
return this.#rich ? this.#sampleSequence : null;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* Event-shaped access for the local profiler harness. Polling this ring once
|
|
564
|
+
* per animation frame would put the harness itself into the sampled stacks.
|
|
565
|
+
* Normal stages cannot subscribe because rich diagnostics are off, and an
|
|
566
|
+
* empty listener set adds no per-presentation work.
|
|
567
|
+
*/
|
|
568
|
+
subscribeDebugSample(
|
|
569
|
+
listener: (sample: StagePresentationSample) => void,
|
|
570
|
+
): (() => void) | null {
|
|
571
|
+
if (!this.#rich) return null;
|
|
572
|
+
this.#debugSampleListeners.add(listener);
|
|
573
|
+
return () => this.#debugSampleListeners.delete(listener);
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
#pushRichSample(sample: Omit<StagePresentationSample, "sequence">): void {
|
|
577
|
+
if (this.#richSamples.length >= this.#richLatencyCapacity) this.#richSamples.shift();
|
|
578
|
+
this.#sampleSequence += 1;
|
|
579
|
+
const row = { sequence: this.#sampleSequence, ...sample };
|
|
580
|
+
this.#richSamples.push(row);
|
|
581
|
+
for (const listener of this.#debugSampleListeners) {
|
|
582
|
+
try {
|
|
583
|
+
listener({ ...row });
|
|
584
|
+
} catch {
|
|
585
|
+
// Diagnostics cannot break the runtime's onPresented callback.
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/** Close buckets strictly before an event, so a presentation exactly on the
|
|
591
|
+
* boundary completes the bucket whose cadence it satisfies. */
|
|
592
|
+
#rollWindowsBefore(now: number): void {
|
|
593
|
+
while (this.#playing && now > this.#windowStartedAt + ADAPTIVE_WINDOW_MS) {
|
|
594
|
+
this.#finishWindow();
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/** Close the boundary bucket after its presentation has been attributed. */
|
|
599
|
+
#rollWindowsThrough(now: number): void {
|
|
600
|
+
while (this.#playing && now >= this.#windowStartedAt + ADAPTIVE_WINDOW_MS) {
|
|
601
|
+
this.#finishWindow();
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
#finishWindow(): void {
|
|
606
|
+
const durationMs = ADAPTIVE_WINDOW_MS;
|
|
607
|
+
const cadenceTotal = this.#expectedFrameRemainder + this.#fpsNumerator;
|
|
608
|
+
const wholeExpectedFrames = Math.floor(cadenceTotal / this.#fpsDenominator + 1e-9);
|
|
609
|
+
this.#expectedFrameRemainder =
|
|
610
|
+
cadenceTotal - wholeExpectedFrames * this.#fpsDenominator;
|
|
611
|
+
// The rational accumulator above is exact, so the reported cadence stays
|
|
612
|
+
// honest across fractional rates. Nothing judges it any more: the window
|
|
613
|
+
// is a readout, and the authored expectation is the honest denominator.
|
|
614
|
+
const expectedFrames = Math.max(1, wholeExpectedFrames);
|
|
615
|
+
// A window nobody could see is not evidence about this scene. Close the
|
|
616
|
+
// bucket and forget what it held.
|
|
617
|
+
if (!this.#visible()) {
|
|
618
|
+
this.#windowLatencies.length = 0;
|
|
619
|
+
this.#windowGpu.length = 0;
|
|
620
|
+
this.#windowPresentedFrames = 0;
|
|
621
|
+
this.#windowDroppedPublications = 0;
|
|
622
|
+
this.#windowStartedAt += ADAPTIVE_WINDOW_MS;
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
const p95 = percentile(this.#windowLatencies, 0.95);
|
|
626
|
+
this.#lastWindowP95Ms = p95;
|
|
627
|
+
this.#lastWindowPresentedFrames = this.#windowPresentedFrames;
|
|
628
|
+
this.#lastWindowExpectedFrames = expectedFrames;
|
|
629
|
+
this.#lastWindowDroppedPublications = this.#windowDroppedPublications;
|
|
630
|
+
// READ BEFORE THE RESET: the three counters below are zeroed on the next
|
|
631
|
+
// line, and a listener that read them from the instance afterwards would
|
|
632
|
+
// report an empty window.
|
|
633
|
+
const closed: StagePerformanceWindow = {
|
|
634
|
+
presentedFrames: this.#windowPresentedFrames,
|
|
635
|
+
expectedFrames,
|
|
636
|
+
droppedPublications: this.#windowDroppedPublications,
|
|
637
|
+
durationMs,
|
|
638
|
+
gpuP95Ms: percentile(this.#windowGpu, 0.95),
|
|
639
|
+
};
|
|
640
|
+
this.#windowLatencies.length = 0;
|
|
641
|
+
this.#windowGpu.length = 0;
|
|
642
|
+
this.#windowPresentedFrames = 0;
|
|
643
|
+
this.#windowDroppedPublications = 0;
|
|
644
|
+
this.#windowStartedAt += ADAPTIVE_WINDOW_MS;
|
|
645
|
+
this.#notifyWindow(closed);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
#notifyPresented(requestedAt: number, frame: number): void {
|
|
649
|
+
for (const listener of this.#presentedListeners) {
|
|
650
|
+
try {
|
|
651
|
+
listener(requestedAt, frame);
|
|
652
|
+
} catch {
|
|
653
|
+
// The window listeners' rule: a reader never breaks the runtime's
|
|
654
|
+
// presentation path.
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
#notifyWindow(window: StagePerformanceWindow): void {
|
|
660
|
+
for (const listener of this.#windowListeners) {
|
|
661
|
+
try {
|
|
662
|
+
listener(window);
|
|
663
|
+
} catch {
|
|
664
|
+
// A readout cannot break the runtime's presentation path, which is the
|
|
665
|
+
// same rule the sample listeners above follow.
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/** Nearest-rank percentile, total over an empty or non-finite sample set. */
|
|
672
|
+
export function percentile(values: readonly number[], quantile: number): number | null {
|
|
673
|
+
const finite = values.filter(Number.isFinite).sort((a, b) => a - b);
|
|
674
|
+
if (finite.length === 0) return null;
|
|
675
|
+
const q = Math.max(0, Math.min(1, quantile));
|
|
676
|
+
const index = Math.max(0, Math.ceil(q * finite.length) - 1);
|
|
677
|
+
return finite[index] ?? null;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
export function stagePerformanceInterval(
|
|
681
|
+
before: StagePerformanceSnapshot,
|
|
682
|
+
after: StagePerformanceSnapshot,
|
|
683
|
+
renderPasses: number,
|
|
684
|
+
): StagePerformanceInterval {
|
|
685
|
+
const presentedFrames = Math.max(0, after.presentedFrames - before.presentedFrames);
|
|
686
|
+
return {
|
|
687
|
+
requestedFrames: Math.max(0, after.requestedFrames - before.requestedFrames),
|
|
688
|
+
publishedFrames: Math.max(0, after.publishedFrames - before.publishedFrames),
|
|
689
|
+
presentedFrames,
|
|
690
|
+
matchedPresentations: Math.max(0, after.matchedPresentations - before.matchedPresentations),
|
|
691
|
+
unmatchedPresentations: Math.max(0, after.unmatchedPresentations - before.unmatchedPresentations),
|
|
692
|
+
droppedPublications: Math.max(0, after.droppedPublications - before.droppedPublications),
|
|
693
|
+
requestCoalesced: Math.max(0, after.requestCoalesced - before.requestCoalesced),
|
|
694
|
+
publicationCoalesced: Math.max(0, after.publicationCoalesced - before.publicationCoalesced),
|
|
695
|
+
renderPasses: Math.max(0, renderPasses),
|
|
696
|
+
effectivePasses: presentedFrames > 0 ? Math.max(0, renderPasses) / presentedFrames : 0,
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
const latencyText = (value: number | null): string => (value === null ? "-" : value.toFixed(1));
|
|
701
|
+
|
|
702
|
+
/** One compact diagnostic segment consumed by PerfHud and benchmark capture. */
|
|
703
|
+
export function stagePerformanceLine(
|
|
704
|
+
snapshot: StagePerformanceSnapshot,
|
|
705
|
+
interval: StagePerformanceInterval,
|
|
706
|
+
quality: PresentationQuality,
|
|
707
|
+
): string {
|
|
708
|
+
const dpr = quality.dpr.toFixed(2);
|
|
709
|
+
return (
|
|
710
|
+
`frames req ${interval.requestedFrames}/pub ${interval.publishedFrames}/pres ${interval.presentedFrames}` +
|
|
711
|
+
` · match ${interval.matchedPresentations}/unmatched ${interval.unmatchedPresentations}` +
|
|
712
|
+
` · coal req ${interval.requestCoalesced}/pub ${interval.publicationCoalesced}/drop ${interval.droppedPublications}` +
|
|
713
|
+
` · lat p50 ${latencyText(snapshot.latencyP50Ms)}/p95 ${latencyText(snapshot.latencyP95Ms)}/p99 ${latencyText(snapshot.latencyP99Ms)}ms` +
|
|
714
|
+
` · passes ${interval.renderPasses} · effective ${interval.effectivePasses.toFixed(2)}` +
|
|
715
|
+
` · q L${quality.level} dpr ${dpr} raster ${quality.rasterScale}`
|
|
716
|
+
);
|
|
717
|
+
}
|