From 25124aed94f83e09f85c6b4b8424773c719416de Mon Sep 17 00:00:00 2001 From: Zyie <24736175+Zyie@users.noreply.github.com> Date: Wed, 11 Sep 2024 20:09:58 +0100 Subject: [PATCH] fix: ensure devtool is reset correctly when closed (#44) --- .github/workflows/main.yml | 12 +-- packages/backend/src/pixi.ts | 8 ++ .../backend/src/rendering/instructions.ts | 2 +- packages/backend/src/rendering/rendering.ts | 36 ++++++-- packages/backend/src/scene/overlay/overlay.ts | 6 +- packages/backend/src/scene/tree/tree.ts | 4 + packages/backend/src/utils/loop.ts | 4 + ...ctive-128.png => pixi-icon-active-128.png} | Bin ...-active-16.png => pixi-icon-active-16.png} | Bin ...-active-48.png => pixi-icon-active-48.png} | Bin ...ive-128.png => pixi-icon-inactive-128.png} | Bin ...ctive-16.png => pixi-icon-inactive-16.png} | Bin ...ctive-48.png => pixi-icon-inactive-48.png} | Bin packages/devtool-chrome/manifest.dev.json | 16 ++-- packages/devtool-chrome/manifest.json | 14 +-- .../devtool-chrome/src/background/index.ts | 28 +++--- packages/devtool-chrome/src/content/index.ts | 6 ++ .../devtool-chrome/src/devtools/devtools.ts | 18 +++- .../devtools/{index.html => pixi-index.html} | 0 packages/devtool-chrome/src/inject/close.ts | 3 + packages/devtool-chrome/vite.chrome.config.ts | 1 + packages/devtool-chrome/vite.inject.config.ts | 2 +- packages/frontend/src/App.tsx | 82 ++++++++++++------ .../src/components/smooth-charts/stat.tsx | 2 +- packages/frontend/src/pages/assets/assets.ts | 9 +- .../frontend/src/pages/rendering/rendering.ts | 20 ++--- .../src/pages/scene/scene-section/Tree.tsx | 1 + packages/frontend/src/pages/scene/state.ts | 18 ++-- packages/frontend/src/types.ts | 19 +++- 29 files changed, 219 insertions(+), 92 deletions(-) rename packages/devtool-chrome/assets/{icon-active-128.png => pixi-icon-active-128.png} (100%) rename packages/devtool-chrome/assets/{icon-active-16.png => pixi-icon-active-16.png} (100%) rename packages/devtool-chrome/assets/{icon-active-48.png => pixi-icon-active-48.png} (100%) rename packages/devtool-chrome/assets/{icon-inactive-128.png => pixi-icon-inactive-128.png} (100%) rename packages/devtool-chrome/assets/{icon-inactive-16.png => pixi-icon-inactive-16.png} (100%) rename packages/devtool-chrome/assets/{icon-inactive-48.png => pixi-icon-inactive-48.png} (100%) rename packages/devtool-chrome/src/devtools/{index.html => pixi-index.html} (100%) create mode 100644 packages/devtool-chrome/src/inject/close.ts diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b675b39..7b1505a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,13 +11,13 @@ jobs: name: Build runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install xvfb run: sudo apt-get install xvfb - - name: Use Node.js 18.x - uses: actions/setup-node@v3 + - name: Use Node.js 20.x + uses: actions/setup-node@v4 with: - node-version: 18 + node-version: 20 - name: Install Dependencies run: npm ci @@ -32,7 +32,7 @@ jobs: - name: Upload chrome extension if: github.event_name != 'release' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: upload path: .upload/ @@ -40,6 +40,6 @@ jobs: # Automatically attach files to release - name: Upload to Release if: github.event_name == 'release' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: files: .upload/* diff --git a/packages/backend/src/pixi.ts b/packages/backend/src/pixi.ts index 09d861d..c719208 100644 --- a/packages/backend/src/pixi.ts +++ b/packages/backend/src/pixi.ts @@ -39,6 +39,7 @@ class PixiWrapper { | 'setBridge' | 'chromeProxy' | 'setChromeProxy' + | 'reset' | keyof TextureState | keyof RenderingState > = { @@ -110,6 +111,7 @@ class PixiWrapper { private _updateThrottle = new Throttle(); private _initialized = false; + private _originalRenderFn: Renderer['render'] | undefined; /** * Searches for a property in the window and its frames. @@ -276,6 +278,8 @@ class PixiWrapper { const that = this; if (this.renderer && !this.renderer.__devtoolInjected) { this.renderer.__devtoolInjected = true; + // store the original render method + this._originalRenderFn = this.renderer.render; this.renderer.render = new Proxy(this.renderer.render, { apply(target, thisArg, ...args) { that.update(); @@ -288,6 +292,10 @@ class PixiWrapper { } public reset() { + this.renderer && this._originalRenderFn && (this.renderer.render = this._originalRenderFn); + this.renderer && (this.renderer.__devtoolInjected = false); + this.rendering.reset(); + this._resetState(); this._devtools = undefined; this._app = undefined; this._stage = undefined; diff --git a/packages/backend/src/rendering/instructions.ts b/packages/backend/src/rendering/instructions.ts index 17c53c6..48e3a35 100644 --- a/packages/backend/src/rendering/instructions.ts +++ b/packages/backend/src/rendering/instructions.ts @@ -35,7 +35,7 @@ export function getBatchInstruction(instruction: Batch, textureCache: TextureCac if (tex) { textures.push(tex); } else { - console.log('Texture not found', texture); + // console.log('Texture not found', texture); } }); diff --git a/packages/backend/src/rendering/rendering.ts b/packages/backend/src/rendering/rendering.ts index 43dbcde..d58fd2e 100644 --- a/packages/backend/src/rendering/rendering.ts +++ b/packages/backend/src/rendering/rendering.ts @@ -27,6 +27,7 @@ import type { TextureSource, TilingSprite, WebGLRenderer, + BatcherPipe, WebGPURenderer, } from 'pixi.js'; import type { PixiDevtools } from '../pixi'; @@ -55,6 +56,10 @@ export class Rendering { private _textureCache: Map = new Map(); private _glDrawFn!: GlGeometrySystem['draw']; + private _batcherBuildStartFn!: BatcherPipe['buildStart']; + private _originalBeginRenderPass!: WebGPURenderer['encoder']['beginRenderPass']; + private _originalRestoreRenderPass!: WebGPURenderer['encoder']['restoreRenderPass']; + private _drawOrder: { pipe: string; drawCalls: number }[] = []; private _pipeExecuteFn: Map['execute']> = new Map(); @@ -71,6 +76,25 @@ export class Rendering { this._devtool = devtool; } + public reset() { + // restore all overriden functions + const renderer = this._devtool.renderer; + + if (!this._batcherBuildStartFn) return; + + renderer.renderPipes.batch.buildStart = this._batcherBuildStartFn; + + if (renderer.type === 0b10) { + const gpuRenderer = renderer as WebGPURenderer; + const encoder = gpuRenderer.encoder; + encoder.beginRenderPass = this._originalBeginRenderPass; + encoder.restoreRenderPass = this._originalRestoreRenderPass; + } else { + const glRenderer = renderer as WebGLRenderer; + glRenderer.geometry.draw = this._glDrawFn; + } + } + public init() { this._textureCache.clear(); this.stats.reset(); @@ -83,9 +107,9 @@ export class Rendering { const renderer = this._devtool.renderer; // override the batcher buildStart function to keep track of rebuild frequency - const batcherBuildStartFn = renderer.renderPipes.batch.buildStart; + this._batcherBuildStartFn = renderer.renderPipes.batch.buildStart; renderer.renderPipes.batch.buildStart = (...args) => { - const res = batcherBuildStartFn.apply(renderer.renderPipes.batch, args); + const res = this._batcherBuildStartFn.apply(renderer.renderPipes.batch, args); this._rebuilt = true; return res; @@ -116,18 +140,18 @@ export class Rendering { }; }; const encoder = gpuRenderer.encoder; - const originalBeginRenderPass = encoder.beginRenderPass; + this._originalBeginRenderPass = encoder.beginRenderPass; encoder.beginRenderPass = (...args) => { - const res = originalBeginRenderPass.apply(encoder, args); + const res = this._originalBeginRenderPass.apply(encoder, args); drawOverride('draw'); drawOverride('drawIndexed'); return res; }; - const originalRestoreRenderPass = encoder.restoreRenderPass; + this._originalRestoreRenderPass = encoder.restoreRenderPass; encoder.restoreRenderPass = (...args) => { - const res = originalRestoreRenderPass.apply(encoder, args); + const res = this._originalRestoreRenderPass.apply(encoder, args); drawOverride('draw'); drawOverride('drawIndexed'); diff --git a/packages/backend/src/scene/overlay/overlay.ts b/packages/backend/src/scene/overlay/overlay.ts index 43a5b61..63e6b6a 100644 --- a/packages/backend/src/scene/overlay/overlay.ts +++ b/packages/backend/src/scene/overlay/overlay.ts @@ -46,13 +46,13 @@ export class Overlay { const newCanvas = this._devtool.canvas!; + this._highlightEnabled = this._devtool.state.overlayHighlightEnabled; + this._pickerEnabled = this._devtool.state.overlayPickerEnabled; + if (newCanvas === this._canvas) { return; } - this._highlightEnabled = this._devtool.state.overlayHighlightEnabled; - this._pickerEnabled = this._devtool.state.overlayPickerEnabled; - this._canvas = newCanvas; this._buildOverlay(); this._selectedStylesExt = getExtensionProp(Overlay.extensions, 'getSelectedStyle'); diff --git a/packages/backend/src/scene/tree/tree.ts b/packages/backend/src/scene/tree/tree.ts index 2a5752a..9f2200c 100644 --- a/packages/backend/src/scene/tree/tree.ts +++ b/packages/backend/src/scene/tree/tree.ts @@ -39,6 +39,10 @@ export class Tree { this._onSwapExtensions = getExtensionsProp(Tree.extensions, 'onSwap'); this._onSelectedExtensions = getExtensionsProp(Tree.extensions, 'onSelected'); this._treePanelButtons = getExtensionsProp(Tree.extensions, 'panelButtons'); + + this.selectedNode = null; + this._idMap.clear(); + this._sceneGraph.clear(); } public nodeButtonPress(nodeId: string, buttonAction: string, value?: boolean) { diff --git a/packages/backend/src/utils/loop.ts b/packages/backend/src/utils/loop.ts index 77dafcb..dddd3d8 100644 --- a/packages/backend/src/utils/loop.ts +++ b/packages/backend/src/utils/loop.ts @@ -15,6 +15,10 @@ export function loop(options: LoopOptions) { function loopRecursive(container: Container, opts: Omit) { const { loop, test } = opts; + if (!container) { + return; + } + const testResult = test?.(container, container.parent) ?? true; if (testResult === false) { diff --git a/packages/devtool-chrome/assets/icon-active-128.png b/packages/devtool-chrome/assets/pixi-icon-active-128.png similarity index 100% rename from packages/devtool-chrome/assets/icon-active-128.png rename to packages/devtool-chrome/assets/pixi-icon-active-128.png diff --git a/packages/devtool-chrome/assets/icon-active-16.png b/packages/devtool-chrome/assets/pixi-icon-active-16.png similarity index 100% rename from packages/devtool-chrome/assets/icon-active-16.png rename to packages/devtool-chrome/assets/pixi-icon-active-16.png diff --git a/packages/devtool-chrome/assets/icon-active-48.png b/packages/devtool-chrome/assets/pixi-icon-active-48.png similarity index 100% rename from packages/devtool-chrome/assets/icon-active-48.png rename to packages/devtool-chrome/assets/pixi-icon-active-48.png diff --git a/packages/devtool-chrome/assets/icon-inactive-128.png b/packages/devtool-chrome/assets/pixi-icon-inactive-128.png similarity index 100% rename from packages/devtool-chrome/assets/icon-inactive-128.png rename to packages/devtool-chrome/assets/pixi-icon-inactive-128.png diff --git a/packages/devtool-chrome/assets/icon-inactive-16.png b/packages/devtool-chrome/assets/pixi-icon-inactive-16.png similarity index 100% rename from packages/devtool-chrome/assets/icon-inactive-16.png rename to packages/devtool-chrome/assets/pixi-icon-inactive-16.png diff --git a/packages/devtool-chrome/assets/icon-inactive-48.png b/packages/devtool-chrome/assets/pixi-icon-inactive-48.png similarity index 100% rename from packages/devtool-chrome/assets/icon-inactive-48.png rename to packages/devtool-chrome/assets/pixi-icon-inactive-48.png diff --git a/packages/devtool-chrome/manifest.dev.json b/packages/devtool-chrome/manifest.dev.json index 7ef628a..2336d0f 100644 --- a/packages/devtool-chrome/manifest.dev.json +++ b/packages/devtool-chrome/manifest.dev.json @@ -2,9 +2,9 @@ "name": "DEV: PixiJS DevTools", "action": { "default_icon": { - "16": "icon-inactive-16.png", - "48": "icon-inactive-48.png", - "128": "icon-inactive-128.png" + "16": "pixi-icon-inactive-16.png", + "48": "pixi-icon-inactive-48.png", + "128": "pixi-icon-inactive-128.png" } }, "content_scripts": [ @@ -20,15 +20,15 @@ "type": "module" }, "icons": { - "16": "icon-active-16.png", - "48": "icon-active-48.png", - "128": "icon-active-128.png" + "16": "pixi-icon-active-16.png", + "48": "pixi-icon-active-48.png", + "128": "pixi-icon-active-128.png" }, "permissions": ["activeTab"], - "devtools_page": "devtools/index.html", + "devtools_page": "devtools/pixi-index.html", "web_accessible_resources": [ { - "resources": ["contentStyle.css", "icon-active-128.png", "icon-inactive-48.png"], + "resources": ["contentStyle.css", "pixi-icon-active-128.png", "pixi-icon-inactive-48.png"], "matches": [""] } ] diff --git a/packages/devtool-chrome/manifest.json b/packages/devtool-chrome/manifest.json index f67b18b..fb77242 100644 --- a/packages/devtool-chrome/manifest.json +++ b/packages/devtool-chrome/manifest.json @@ -4,9 +4,9 @@ "description": "DevTools for PixiJS", "action": { "default_icon": { - "16": "icon-inactive-16.png", - "48": "icon-inactive-48.png", - "128": "icon-inactive-128.png" + "16": "pixi-icon-inactive-16.png", + "48": "pixi-icon-inactive-48.png", + "128": "pixi-icon-inactive-128.png" } }, "content_scripts": [ @@ -22,10 +22,10 @@ "type": "module" }, "icons": { - "16": "icon-active-16.png", - "48": "icon-active-48.png", - "128": "icon-active-128.png" + "16": "pixi-icon-active-16.png", + "48": "pixi-icon-active-48.png", + "128": "pixi-icon-active-128.png" }, "permissions": ["activeTab"], - "devtools_page": "devtools/index.html" + "devtools_page": "devtools/pixi-index.html" } diff --git a/packages/devtool-chrome/src/background/index.ts b/packages/devtool-chrome/src/background/index.ts index 8821b57..7a4c72b 100644 --- a/packages/devtool-chrome/src/background/index.ts +++ b/packages/devtool-chrome/src/background/index.ts @@ -4,6 +4,7 @@ import { convertPostMessage, convertPostMessageData } from '../messageUtils'; interface Message { method: DevtoolMessage; data: string; + tabId?: number; } interface IconPath { @@ -13,9 +14,9 @@ interface IconPath { function setIconAndPopup(type: DevtoolMessage, tabId: number) { const state = type === DevtoolMessage.active ? 'active' : 'inactive'; const iconPath: IconPath = { - 16: `./icon-${state}-16.png`, - 48: `./icon-${state}-48.png`, - 128: `./icon-${state}-128.png`, + 16: `./pixi-icon-${state}-16.png`, + 48: `./pixi-icon-${state}-48.png`, + 128: `./pixi-icon-${state}-128.png`, }; const title = type === DevtoolMessage.inactive ? 'PixiJS not active on this page' : 'PixiJS active on this page'; @@ -48,6 +49,8 @@ chrome.runtime.onConnect.addListener(function (port) { const tabs = Object.keys(devtoolConnections); for (let i = 0, len = tabs.length; i < len; i++) { if (devtoolConnections[tabs[i]] === port) { + const tabId = parseInt(tabs[i], 10); + chrome.tabs.sendMessage(tabId, { id: 'pixi-devtools', method: 'panelClosed' }); delete devtoolConnections[tabs[i]]; break; } @@ -64,23 +67,21 @@ chrome.runtime.onMessage.addListener((request: Message, sender: chrome.runtime.M } // Messages from content scripts should have sender.tab set - if (sender.tab) { - const tabId = sender.tab.id!; + if (sender.tab || request.tabId) { + const tabId = sender.tab?.id ?? request.tabId!; if (tabId in devtoolConnections) { const message = convertPostMessage(converted.method, converted.data); devtoolConnections[tabId].postMessage({ id: 'pixi-devtools', - tabId: sender.tab?.id, + tabId: sender.tab?.id ?? request.tabId, ...message, }); // Send a response back to the sender sendResponse({ status: 'success' }); } else { - console.log('Tab not found in connection list.'); sendResponse({ status: 'error', message: 'Tab not found in connection list.' }); } } else { - console.log('sender.tab not defined.'); sendResponse({ status: 'error', message: 'sender.tab not defined.' }); } return true; @@ -100,8 +101,15 @@ chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { } } if (tab.active && changeInfo.status === 'complete') { - // TODO: we can send a message to the devtools to reload the page - console.log(`Tab ${tabId} has reloaded.`); + // send a message to the devtools to reload the page + if (tabId in devtoolConnections) { + const message = convertPostMessage(DevtoolMessage.pageReload, {}); + devtoolConnections[tabId].postMessage({ + id: 'pixi-devtools', + tabId, + ...message, + }); + } // You can perform additional actions here } }); diff --git a/packages/devtool-chrome/src/content/index.ts b/packages/devtool-chrome/src/content/index.ts index fff85fd..88ed012 100644 --- a/packages/devtool-chrome/src/content/index.ts +++ b/packages/devtool-chrome/src/content/index.ts @@ -41,3 +41,9 @@ window.addEventListener( }, false, ); + +chrome.runtime.onMessage.addListener(function (message) { + if (message.method === 'panelClosed') { + injectScript(chrome.runtime.getURL('inject/index2.js'), 'body'); + } +}); diff --git a/packages/devtool-chrome/src/devtools/devtools.ts b/packages/devtool-chrome/src/devtools/devtools.ts index 9763349..dc90623 100644 --- a/packages/devtool-chrome/src/devtools/devtools.ts +++ b/packages/devtool-chrome/src/devtools/devtools.ts @@ -1,5 +1,21 @@ +import { DevtoolMessage } from '@devtool/frontend/types'; +import { convertPostMessage } from '../messageUtils'; + chrome.devtools.panels.create( import.meta.env.DEV ? 'Dev: PixiJS DevTools' : 'PixiJS DevTools', - 'icon-active-128.png', + 'pixi-icon-active-128.png', 'devtools/panel/panel.html', + (panel) => { + const tabId = chrome.devtools.inspectedWindow.tabId; + + panel.onShown.addListener(() => { + const message = convertPostMessage(DevtoolMessage.panelShown, {}); + chrome.runtime.sendMessage({ ...message, tabId }); + }); + + panel.onHidden.addListener(() => { + const message = convertPostMessage(DevtoolMessage.panelHidden, {}); + chrome.runtime.sendMessage({ ...message, tabId }); + }); + }, ); diff --git a/packages/devtool-chrome/src/devtools/index.html b/packages/devtool-chrome/src/devtools/pixi-index.html similarity index 100% rename from packages/devtool-chrome/src/devtools/index.html rename to packages/devtool-chrome/src/devtools/pixi-index.html diff --git a/packages/devtool-chrome/src/inject/close.ts b/packages/devtool-chrome/src/inject/close.ts new file mode 100644 index 0000000..9bd5b07 --- /dev/null +++ b/packages/devtool-chrome/src/inject/close.ts @@ -0,0 +1,3 @@ +window.__PIXI_DEVTOOLS_WRAPPER__?.overlay.enableHighlight(false); +window.__PIXI_DEVTOOLS_WRAPPER__?.overlay.enablePicker(false); +window.__PIXI_DEVTOOLS_WRAPPER__?.reset(); diff --git a/packages/devtool-chrome/vite.chrome.config.ts b/packages/devtool-chrome/vite.chrome.config.ts index 68a7750..173834d 100644 --- a/packages/devtool-chrome/vite.chrome.config.ts +++ b/packages/devtool-chrome/vite.chrome.config.ts @@ -45,6 +45,7 @@ export default defineConfig((config) => { const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')); const resource = manifest.web_accessible_resources[0]; resource.resources.push('inject/index.js'); + resource.resources.push('inject/index2.js'); fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2)); } }, diff --git a/packages/devtool-chrome/vite.inject.config.ts b/packages/devtool-chrome/vite.inject.config.ts index bae2bbc..af5c996 100644 --- a/packages/devtool-chrome/vite.inject.config.ts +++ b/packages/devtool-chrome/vite.inject.config.ts @@ -28,7 +28,7 @@ export default defineConfig((config) => { ], build: { lib: { - entry: 'inject/index.ts', + entry: ['inject/index.ts', 'inject/close.ts'], fileName: 'index', formats: ['es'], }, diff --git a/packages/frontend/src/App.tsx b/packages/frontend/src/App.tsx index 712e02e..6d61581 100644 --- a/packages/frontend/src/App.tsx +++ b/packages/frontend/src/App.tsx @@ -12,10 +12,10 @@ import { createSelectors, isDifferent } from './lib/utils'; import { AssetsPanel } from './pages/assets/AssetsPanel'; import { RenderingPanel } from './pages/rendering/RenderingPanel'; import { ScenePanel } from './pages/scene/ScenePanel'; -import { sceneStateSlice } from './pages/scene/state'; -import type { DevtoolState } from './types'; -import { textureStateSlice } from './pages/assets/assets'; -import { renderingStateSlice } from './pages/rendering/rendering'; +import { sceneStateSelectors, sceneStateSlice } from './pages/scene/state'; +import type { DevtoolMessage, DevtoolState, DevtoolStateSelectors } from './types'; +import { textureStateSelectors, textureStateSlice } from './pages/assets/assets'; +import { renderingStateSelectors, renderingStateSlice } from './pages/rendering/rendering'; const tabComponents = { Scene: , @@ -23,38 +23,44 @@ const tabComponents = { Rendering: , } as const; +const initialState: DevtoolStateSelectors = { + active: false, + version: null, + sceneGraph: { + id: 'root', + name: 'root', + children: [], + metadata: { + type: 'Container', + }, + }, + sceneTreeData: { + buttons: [], + }, + ...sceneStateSelectors, + ...textureStateSelectors, + ...renderingStateSelectors, +}; + export const useDevtoolStore = createSelectors( create((set) => ({ - active: false, + ...initialState, setActive: (active: boolean) => set({ active }), - chromeProxy: null, setChromeProxy: (chromeProxy: DevtoolState['chromeProxy']) => set({ chromeProxy }), - - version: null, setVersion: (version: DevtoolState['version']) => set({ version }), - - sceneGraph: { - id: 'root', - name: 'root', - children: [], - metadata: { - type: 'Container', - }, - }, setSceneGraph: (sceneGraph: DevtoolState['sceneGraph']) => set({ sceneGraph }), - bridge: null, setBridge: (bridge: DevtoolState['bridge']) => set({ bridge }), - - sceneTreeData: { - buttons: [], - }, setSceneTreeData: (data: DevtoolState['sceneTreeData']) => set({ sceneTreeData: data }), ...sceneStateSlice(set), ...textureStateSlice(set), ...renderingStateSlice(set), + + reset: () => { + set(initialState); + }, })), ); @@ -74,21 +80,43 @@ const App: React.FC = ({ bridge, chromeProxy }) => { const setActiveProps = useDevtoolStore.use.setActiveProps(); const setOverlayPickerEnabled = useDevtoolStore.use.setOverlayPickerEnabled(); const setSceneTreeData = useDevtoolStore.use.setSceneTreeData(); + const reset = useDevtoolStore.use.reset(); + const setOverlayHighlightEnabled = useDevtoolStore.use.setOverlayHighlightEnabled(); useEffect(() => { setBridge(bridge); setChromeProxy(chromeProxy); - bridge('window.__PIXI_DEVTOOLS_WRAPPER__.inject()'); - const devToolsConnection = chromeProxy.runtime.connect({ name: 'devtools-connection' }); devToolsConnection.postMessage({ name: 'init', tabId: chromeProxy.devtools.inspectedWindow.tabId, }); + bridge('window.__PIXI_DEVTOOLS_WRAPPER__.inject()'); + bridge(`window.__PIXI_DEVTOOLS_WRAPPER__?.overlay.enableHighlight(true)`); + devToolsConnection.onMessage.addListener((message) => { - switch (message.method) { + switch (message.method as DevtoolMessage) { + case 'devtool:panelShown': + { + // TODO: consider re-enabling the overlay picker when the panel is shown + } + break; + case 'devtool:panelHidden': + { + // disable the overlay picker when the panel is hidden + // set previous highlight state + bridge(`window.__PIXI_DEVTOOLS_WRAPPER__?.overlay.enablePicker(false)`); + bridge(`window.__PIXI_DEVTOOLS_WRAPPER__?.overlay.enableHighlight(false)`); + } + break; + case 'devtool:pageReload': + { + reset(); + bridge('window.__PIXI_DEVTOOLS_WRAPPER__.inject()'); + } + break; case 'pixi-active': { setActive(true); @@ -120,6 +148,8 @@ const App: React.FC = ({ bridge, chromeProxy }) => { isDifferent(currentState.activeProps, data.activeProps) && setActiveProps(data.activeProps); isDifferent(currentState.overlayPickerEnabled, data.overlayPickerEnabled) && setOverlayPickerEnabled(data.overlayPickerEnabled); + isDifferent(currentState.overlayHighlightEnabled, data.overlayHighlightEnabled) && + setOverlayHighlightEnabled(data.overlayHighlightEnabled); isDifferent(currentState.sceneTreeData, data.sceneTreeData) && setSceneTreeData(data.sceneTreeData); } break; @@ -138,6 +168,8 @@ const App: React.FC = ({ bridge, chromeProxy }) => { setVersion, setOverlayPickerEnabled, setSceneTreeData, + reset, + setOverlayHighlightEnabled, ]); const windowString = `window.__PIXI_DEVTOOLS__ = { diff --git a/packages/frontend/src/components/smooth-charts/stat.tsx b/packages/frontend/src/components/smooth-charts/stat.tsx index 559839f..aae7114 100644 --- a/packages/frontend/src/components/smooth-charts/stat.tsx +++ b/packages/frontend/src/components/smooth-charts/stat.tsx @@ -118,7 +118,7 @@ export const CanvasStatComponent: React.FC = memo( // Initialize the Panel class with the canvas element panelRef.current = new Panel(canvasRef.current, title, fgColor, bgColor, lineColor, textColor); } - }, []); + }); useEffect(() => { panelRef.current = new Panel(canvasRef.current!, title, fgColor, bgColor, lineColor, textColor); }, [bgColor, fgColor, lineColor, title, textColor]); diff --git a/packages/frontend/src/pages/assets/assets.ts b/packages/frontend/src/pages/assets/assets.ts index 637172d..dbdd7a2 100644 --- a/packages/frontend/src/pages/assets/assets.ts +++ b/packages/frontend/src/pages/assets/assets.ts @@ -1,5 +1,6 @@ import type { ZustSet } from '../../lib/utils'; import type { ALPHA_MODES, TEXTURE_DIMENSIONS, TEXTURE_FORMATS } from 'pixi.js'; +import type { RemoveSetters } from '../../types'; export interface TextureDataState { gpuSize: number; @@ -30,9 +31,11 @@ export interface TextureState { } export const textureStateSlice = (set: ZustSet) => ({ - selectedTexture: null, setSelectedTexture: (texture: TextureDataState | null) => set((state) => ({ ...state, selectedTexture: texture })), - - textures: [], setTextures: (textures: TextureDataState[]) => set((state) => ({ ...state, textures })), }); + +export const textureStateSelectors: RemoveSetters = { + selectedTexture: null, + textures: [], +}; diff --git a/packages/frontend/src/pages/rendering/rendering.ts b/packages/frontend/src/pages/rendering/rendering.ts index 058bf89..7df9d12 100644 --- a/packages/frontend/src/pages/rendering/rendering.ts +++ b/packages/frontend/src/pages/rendering/rendering.ts @@ -1,4 +1,5 @@ import type { ZustSet } from '../../lib/utils'; +import type { RemoveSetters } from '../../types'; import type { BaseInstruction, BatchInstruction, @@ -87,24 +88,23 @@ export interface RenderingState { } export const renderingStateSlice = (set: ZustSet) => ({ - selectedInstruction: null, setSelectedInstruction: (instruction: RenderingState['selectedInstruction']) => set((state) => ({ ...state, selectedInstruction: instruction })), - - renderingData: null, setRenderingData: (data: RenderingState['renderingData']) => set((state) => ({ ...state, renderingData: data })), - - frameCaptureData: null, setFrameCaptureData: (data: RenderingState['frameCaptureData']) => set((state) => ({ ...state, frameCaptureData: data })), - captureWithScreenshot: true, setCaptureWithScreenshot: (value: boolean) => set((state) => ({ ...state, captureWithScreenshot: value })), - - disableCaptureWithScreenshot: false, setDisableCaptureWithScreenshot: (value: boolean) => set((state) => ({ ...state, disableCaptureWithScreenshot: value })), - - canvasData: null, setCanvasData: (data: RenderingState['canvasData']) => set((state) => ({ ...state, canvasData: data })), }); + +export type PermanentRenderingStateKeys = 'captureWithScreenshot'; +export const renderingStateSelectors: Omit, PermanentRenderingStateKeys> = { + selectedInstruction: null, + renderingData: null, + frameCaptureData: null, + disableCaptureWithScreenshot: false, + canvasData: null, +}; diff --git a/packages/frontend/src/pages/scene/scene-section/Tree.tsx b/packages/frontend/src/pages/scene/scene-section/Tree.tsx index 889cb5f..15b22b6 100644 --- a/packages/frontend/src/pages/scene/scene-section/Tree.tsx +++ b/packages/frontend/src/pages/scene/scene-section/Tree.tsx @@ -69,6 +69,7 @@ const Panel: React.FC = ({ children, onSearch, panelButtons }) => { size="icon" className="hover:border-primary h-8 rounded-none hover:border-b-2" defaultPressed={overlayHighlightEnabled} + pressed={overlayHighlightEnabled} onPressedChange={onHighlightToggle} >
diff --git a/packages/frontend/src/pages/scene/state.ts b/packages/frontend/src/pages/scene/state.ts index a8ab204..130113b 100644 --- a/packages/frontend/src/pages/scene/state.ts +++ b/packages/frontend/src/pages/scene/state.ts @@ -1,5 +1,6 @@ import type { ZustSet } from '../../lib/utils'; import type { PropertyPanelData } from '../../components/properties/propertyTypes'; +import type { RemoveSetters } from '../../types'; export interface SceneState { stats: Record | null; @@ -19,19 +20,18 @@ export interface SceneState { } export const sceneStateSlice = (set: ZustSet) => ({ - stats: null, setStats: (stats: SceneState['stats']) => set({ stats: stats }), - - selectedNode: null, setSelectedNode: (nodeId: SceneState['selectedNode']) => set({ selectedNode: nodeId }), - - activeProps: [], setActiveProps: (props: SceneState['activeProps']) => set({ activeProps: props }), - - overlayPickerEnabled: false, setOverlayPickerEnabled: (enabled: SceneState['overlayPickerEnabled']) => set({ overlayPickerEnabled: enabled }), - - overlayHighlightEnabled: true, setOverlayHighlightEnabled: (enabled: SceneState['overlayHighlightEnabled']) => set({ overlayHighlightEnabled: enabled }), }); + +export const sceneStateSelectors: RemoveSetters = { + stats: null, + selectedNode: null, + activeProps: [], + overlayPickerEnabled: false, + overlayHighlightEnabled: true, +}; diff --git a/packages/frontend/src/types.ts b/packages/frontend/src/types.ts index 09fb491..b596fac 100644 --- a/packages/frontend/src/types.ts +++ b/packages/frontend/src/types.ts @@ -1,6 +1,6 @@ import type { BridgeFn } from './lib/utils'; import type { TextureState } from './pages/assets/assets'; -import type { RenderingState } from './pages/rendering/rendering'; +import type { PermanentRenderingStateKeys, RenderingState } from './pages/rendering/rendering'; import type { SceneState } from './pages/scene/state'; import type { ButtonMetadata, PixiMetadata } from '@pixi/devtools'; @@ -9,6 +9,11 @@ export enum DevtoolMessage { inactive = 'pixi-inactive', stateUpdate = 'pixi-state-update', pulse = 'pixi-pulse', + + panelShown = 'devtool:panelShown', + panelHidden = 'devtool:panelHidden', + + pageReload = 'devtool:pageReload', } export type SceneGraphEntry = { @@ -38,4 +43,16 @@ export interface DevtoolState extends SceneState, TextureState, RenderingState { buttons: ButtonMetadata[]; } | null; setSceneTreeData: (data: DevtoolState['sceneTreeData']) => void; + + reset: () => void; } + +// Utility type to remove properties starting with 'set' +export type RemoveSetters = { + [K in keyof T as K extends `set${string}` ? never : K]: T[K]; +}; + +export type DevtoolStateSelectors = Omit< + RemoveSetters, + 'reset' | 'chromeProxy' | 'bridge' | PermanentRenderingStateKeys +>;