From 6d7bd89f0bb89ae9f9438d12fa5726e2a4d22e40 Mon Sep 17 00:00:00 2001 From: cpsdqs Date: Mon, 9 Oct 2017 21:45:52 +0200 Subject: [PATCH] Start the fancy-debug rewrite --- js/term/debug.js | 457 +++++++++++-------------------------- js/term/screen_layout.js | 2 + js/term/screen_renderer.js | 4 +- sass/pages/_term.scss | 4 +- 4 files changed, 140 insertions(+), 327 deletions(-) diff --git a/js/term/debug.js b/js/term/debug.js index 294da29..9d4b062 100644 --- a/js/term/debug.js +++ b/js/term/debug.js @@ -1,397 +1,208 @@ -const { mk } = require('../utils') - module.exports = function attachDebugger (screen, connection) { - const debugCanvas = mk('canvas') + const debugCanvas = document.createElement('canvas') + debugCanvas.classList.add('debug-canvas') const ctx = debugCanvas.getContext('2d') - debugCanvas.classList.add('debug-canvas') + const toolbar = document.createElement('div') + toolbar.classList.add('debug-toolbar') - let mouseHoverCell = null - let updateToolbar + let selectedCell = null + const onMouseMove = (e) => { + selectedCell = screen.layout.screenToGrid(e.offsetX, e.offsetY) + } + const onMouseOut = (e) => { + selectedCell = null + } - let onMouseMove = e => { - mouseHoverCell = screen.layout.screenToGrid(e.offsetX, e.offsetY) - startDrawing() - updateToolbar() + const updateCanvasSize = function () { + let { width, height, devicePixelRatio } = screen.layout.window + let cellSize = screen.layout.getCellSize() + let padding = Math.round(screen.layout._padding) + debugCanvas.width = (width * cellSize.width + 2 * padding) * devicePixelRatio + debugCanvas.height = (height * cellSize.height + 2 * padding) * devicePixelRatio + debugCanvas.style.width = `${width * cellSize.width + 2 * screen.layout._padding}px` + debugCanvas.style.height = `${height * cellSize.height + 2 * screen.layout._padding}px` } - let onMouseOut = () => (mouseHoverCell = null) - let addCanvas = function () { - if (!debugCanvas.parentNode) { + let startDrawLoop + let screenAttached = false + let eventNode + const setScreenAttached = function (attached) { + if (attached && !debugCanvas.parentNode) { screen.layout.canvas.parentNode.appendChild(debugCanvas) - screen.layout.canvas.addEventListener('mousemove', onMouseMove) - screen.layout.canvas.addEventListener('mouseout', onMouseOut) - } - } - let removeCanvas = function () { - if (debugCanvas.parentNode) { + eventNode = debugCanvas.parentNode + eventNode.addEventListener('mousemove', onMouseMove) + eventNode.addEventListener('mouseout', onMouseOut) + screen.layout.on('size-update', updateCanvasSize) + updateCanvasSize() + screenAttached = true + startDrawLoop() + } else if (!attached && debugCanvas.parentNode) { debugCanvas.parentNode.removeChild(debugCanvas) - screen.layout.canvas.removeEventListener('mousemove', onMouseMove) - screen.layout.canvas.removeEventListener('mouseout', onMouseOut) - onMouseOut() + eventNode.removeEventListener('mousemove', onMouseMove) + eventNode.removeEventListener('mouseout', onMouseOut) + screen.layout.removeListener('size-update', updateCanvasSize) + screenAttached = false } } - let updateCanvasSize = function () { - let { width, height, devicePixelRatio } = screen.layout.window - let cellSize = screen.layout.getCellSize() - debugCanvas.width = width * cellSize.width * devicePixelRatio - debugCanvas.height = height * cellSize.height * devicePixelRatio - debugCanvas.style.width = `${width * cellSize.width}px` - debugCanvas.style.height = `${height * cellSize.height}px` + + const setToolbarAttached = function (attached) { + if (attached && !toolbar.parentNode) { + screen.layout.canvas.parentNode.appendChild(toolbar) + } else if (!attached && toolbar.parentNode) { + screen.layout.canvas.parentNode.removeChild(toolbar) + } } - let drawInfo = mk('div') - drawInfo.classList.add('draw-info') + screen.on('update-window:debug', enabled => { + setToolbarAttached(enabled) + }) - let startTime, endTime, lastReason - let cells = new Map() - let clippedRects = [] - let updateFrames = [] + screen.layout.on('update-window:debug', enabled => { + setScreenAttached(enabled) + }) - let startDrawing + let drawData = { + reason: '', + startTime: 0, + endTime: 0, + clipped: [], + frames: [], + cells: new Map() + } screen._debug = screen.layout.renderer._debug = { drawStart (reason) { - lastReason = reason - startTime = Date.now() - clippedRects = [] + drawData.reason = reason + drawData.startTime = window.performance.now() }, drawEnd () { - endTime = Date.now() - drawInfo.textContent = `Draw: ${lastReason} (${(endTime - startTime)} ms), fancy gfx=${screen.layout.renderer.graphics}` - startDrawing() + drawData.endTime = window.performance.now() }, setCell (cell, flags) { - cells.set(cell, [flags, Date.now()]) - }, - clipRect (...args) { - clippedRects.push(args) + drawData.cells.set(cell, [flags, window.performance.now()]) }, pushFrame (frame) { - frame.push(Date.now()) - updateFrames.push(frame) - startDrawing() + drawData.frames.push([...frame, window.performance.now()]) } } - let clipPattern - { - let patternCanvas = document.createElement('canvas') - patternCanvas.width = patternCanvas.height = 12 - let pctx = patternCanvas.getContext('2d') - pctx.lineWidth = 1 - pctx.strokeStyle = '#00f' - pctx.beginPath() - pctx.moveTo(0, 0) - pctx.lineTo(0 - 4, 12) - pctx.moveTo(4, 0) - pctx.lineTo(4 - 4, 12) - pctx.moveTo(8, 0) - pctx.lineTo(8 - 4, 12) - pctx.moveTo(12, 0) - pctx.lineTo(12 - 4, 12) - pctx.moveTo(16, 0) - pctx.lineTo(16 - 4, 12) - pctx.stroke() - clipPattern = ctx.createPattern(patternCanvas, 'repeat') - } - let isDrawing = false - let lastDrawTime = 0 - let t = 0 - let drawLoop = function () { - if (isDrawing) window.requestAnimationFrame(drawLoop) + if (screenAttached) window.requestAnimationFrame(drawLoop) + else isDrawing = false - let dt = (Date.now() - lastDrawTime) / 1000 - lastDrawTime = Date.now() - t += dt + let now = window.performance.now() - let { devicePixelRatio, width, height } = screen.layout.window - let { width: cellWidth, height: cellHeight } = screen.layout.getCellSize() - let screenLength = width * height - let now = Date.now() + let { width, height, devicePixelRatio } = screen.layout.window + let padding = Math.round(screen.layout._padding) + let cellSize = screen.layout.getCellSize() ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0) - ctx.clearRect(0, 0, width * cellWidth, height * cellHeight) + ctx.clearRect(0, 0, width * cellSize.width + 2 * padding, height * cellSize.height + 2 * padding) + ctx.translate(padding, padding) + + ctx.lineWidth = 2 + ctx.lineJoin = 'round' - let activeCells = 0 - for (let cell = 0; cell < screenLength; cell++) { + const cells = drawData.cells + for (let cell = 0; cell < width * height; cell++) { + // cell does not exist or has no flags set if (!cells.has(cell) || cells.get(cell)[0] === 0) continue - let [flags, timestamp] = cells.get(cell) + const [flags, timestamp] = cells.get(cell) let elapsedTime = (now - timestamp) / 1000 - if (elapsedTime > 1) continue + if (elapsedTime > 1) { + cells.delete(cell) + continue + } - activeCells++ ctx.globalAlpha = 0.5 * Math.max(0, 1 - elapsedTime) let x = cell % width let y = Math.floor(cell / width) - if (flags & 1) { - // redrawn - ctx.fillStyle = '#f0f' - } if (flags & 2) { // updated ctx.fillStyle = '#0f0' + } else if (flags & 1) { + // redrawn + ctx.fillStyle = '#f0f' } - ctx.fillRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight) + if (!(flags & 4)) { + // outside a clipped region + ctx.fillStyle = '#0ff' + } + + ctx.fillRect(x * cellSize.width, y * cellSize.height, cellSize.width, cellSize.height) - if (flags & 4) { + if (flags & 8) { // wide cell - ctx.lineWidth = 2 ctx.strokeStyle = '#f00' - ctx.strokeRect(x * cellWidth, y * cellHeight, cellWidth, cellHeight) + ctx.beginPath() + ctx.moveTo(x * cellSize.width, (y + 1) * cellSize.height) + ctx.lineTo((x + 1) * cellSize.width, (y + 1) * cellSize.height) + ctx.stroke() } } - if (clippedRects.length) { - ctx.globalAlpha = 0.5 - ctx.beginPath() - - for (let rect of clippedRects) { - ctx.rect(...rect) + let framesToDelete = [] + for (let frame of drawData.frames) { + let timestamp = frame[4] + let elapsedTime = (now - timestamp) / 1000 + if (elapsedTime > 1) framesToDelete.push(frame) + else { + ctx.globalAlpha = 1 - elapsedTime + ctx.strokeStyle = '#ff0' + ctx.strokeRect(frame[0] * cellSize.width, frame[1] * cellSize.height, + frame[2] * cellSize.width, frame[3] * cellSize.height) } - - ctx.fillStyle = clipPattern - ctx.fill() } - - let didDrawUpdateFrames = false - if (updateFrames.length) { - let framesToDelete = [] - for (let frame of updateFrames) { - let time = frame[4] - let elapsed = Date.now() - time - if (elapsed > 1000) framesToDelete.push(frame) - else { - didDrawUpdateFrames = true - ctx.globalAlpha = 1 - elapsed / 1000 - ctx.strokeStyle = '#ff0' - ctx.lineWidth = 2 - ctx.strokeRect(frame[0] * cellWidth, frame[1] * cellHeight, frame[2] * cellWidth, frame[3] * cellHeight) - } - } - for (let frame of framesToDelete) { - updateFrames.splice(updateFrames.indexOf(frame), 1) - } + for (let frame of framesToDelete) { + drawData.frames.splice(drawData.frames.indexOf(frame), 1) } - if (mouseHoverCell) { + if (selectedCell !== null) { + let [x, y] = selectedCell + ctx.save() + ctx.globalAlpha = 0.5 + ctx.lineWidth = 1 + + // draw X line + ctx.beginPath() + ctx.moveTo(0, y * cellSize.height) + ctx.lineTo(x * cellSize.width, y * cellSize.height) + ctx.strokeStyle = '#f00' + ctx.setLineDash([cellSize.width]) + ctx.stroke() + + // draw Y line + ctx.beginPath() + ctx.moveTo(x * cellSize.width, 0) + ctx.lineTo(x * cellSize.width, y * cellSize.height) + ctx.strokeStyle = '#0f0' + ctx.setLineDash([cellSize.height]) + ctx.stroke() + ctx.globalAlpha = 1 - ctx.lineWidth = 1 + 0.5 * Math.sin(t * 10) + ctx.lineWidth = 1 + 0.5 * Math.sin((now / 1000) * 10) ctx.strokeStyle = '#fff' ctx.lineJoin = 'round' ctx.setLineDash([2, 2]) - ctx.lineDashOffset = t * 10 - ctx.strokeRect(mouseHoverCell[0] * cellWidth, mouseHoverCell[1] * cellHeight, cellWidth, cellHeight) + ctx.lineDashOffset = (now / 1000) * 10 + ctx.strokeRect(x * cellSize.width, y * cellSize.height, cellSize.width, cellSize.height) ctx.lineDashOffset += 2 ctx.strokeStyle = '#000' - ctx.strokeRect(mouseHoverCell[0] * cellWidth, mouseHoverCell[1] * cellHeight, cellWidth, cellHeight) + ctx.strokeRect(x * cellSize.width, y * cellSize.height, cellSize.width, cellSize.height) ctx.restore() } - - if (activeCells === 0 && !mouseHoverCell && !didDrawUpdateFrames) { - isDrawing = false - removeCanvas() - } } - - startDrawing = function () { + startDrawLoop = function () { if (isDrawing) return - addCanvas() - updateCanvasSize() isDrawing = true - lastDrawTime = Date.now() drawLoop() } - - // debug toolbar - const toolbar = mk('div') - toolbar.classList.add('debug-toolbar') - let toolbarAttached = false - - const heartbeat = mk('div') - heartbeat.classList.add('heartbeat') - heartbeat.textContent = '❤' - toolbar.appendChild(heartbeat) - - const dataDisplay = mk('div') - dataDisplay.classList.add('data-display') - toolbar.appendChild(dataDisplay) - - const internalDisplay = mk('div') - internalDisplay.classList.add('internal-display') - toolbar.appendChild(internalDisplay) - - toolbar.appendChild(drawInfo) - - const buttons = mk('div') - buttons.classList.add('toolbar-buttons') - toolbar.appendChild(buttons) - - // heartbeat - connection.on('heartbeat', () => { - heartbeat.classList.remove('beat') - window.requestAnimationFrame(() => { - heartbeat.classList.add('beat') - }) - }) - - { - const redraw = mk('button') - redraw.textContent = 'Redraw' - redraw.addEventListener('click', e => { - screen.layout.renderer.resetDrawn() - screen.layout.renderer.draw('debug-redraw') - }) - buttons.appendChild(redraw) - - const fancyGraphics = mk('button') - fancyGraphics.textContent = 'Toggle Fancy Graphics' - fancyGraphics.addEventListener('click', e => { - screen.layout.renderer.graphics = +!screen.layout.renderer.graphics - screen.layout.renderer.draw('set-graphics') - }) - buttons.appendChild(fancyGraphics) - } - - const attachToolbar = function () { - screen.layout.canvas.parentNode.appendChild(toolbar) - } - const detachToolbar = function () { - toolbar.parentNode.removeChild(toolbar) - } - - screen.on('update-window:debug', debug => { - if (debug !== toolbarAttached) { - toolbarAttached = debug - if (debug) attachToolbar() - else { - detachToolbar() - removeCanvas() - } - } - }) - - const displayCellAttrs = attrs => { - let result = attrs.toString(16) - if (attrs & 1 || attrs & 2) { - result += ':has(' - if (attrs & 1) result += 'fg' - if (attrs & 2) result += (attrs & 1 ? ',' : '') + 'bg' - result += ')' - } - let attributes = [] - if (attrs & (1 << 2)) attributes.push('\\[bold]bold\\()') - if (attrs & (1 << 3)) attributes.push('\\[underline]underln\\()') - if (attrs & (1 << 4)) attributes.push('\\[invert]invert\\()') - if (attrs & (1 << 5)) attributes.push('blink') - if (attrs & (1 << 6)) attributes.push('\\[italic]italic\\()') - if (attrs & (1 << 7)) attributes.push('\\[strike]strike\\()') - if (attrs & (1 << 8)) attributes.push('\\[overline]overln\\()') - if (attrs & (1 << 9)) attributes.push('\\[faint]faint\\()') - if (attrs & (1 << 10)) attributes.push('fraktur') - if (attributes.length) result += ':' + attributes.join() - return result.trim() - } - - const formatColor = color => color < 256 ? color : `#${`000000${(color - 256).toString(16)}`.substr(-6)}` - const getCellData = cell => { - if (cell < 0 || cell > screen.screen.length) return '(-)' - let cellAttrs = screen.layout.renderer.drawnScreenAttrs[cell] | 0 - let cellFG = screen.layout.renderer.drawnScreenFG[cell] | 0 - let cellBG = screen.layout.renderer.drawnScreenBG[cell] | 0 - let fgText = formatColor(cellFG) - let bgText = formatColor(cellBG) - fgText += `\\[color=${screen.layout.renderer.getColor(cellFG).replace(/ /g, '')}]●\\[]` - bgText += `\\[color=${screen.layout.renderer.getColor(cellBG).replace(/ /g, '')}]●\\[]` - let cellCode = (screen.layout.renderer.drawnScreen[cell] || '').codePointAt(0) | 0 - let hexcode = cellCode.toString(16).toUpperCase() - if (hexcode.length < 4) hexcode = `0000${hexcode}`.substr(-4) - hexcode = `U+${hexcode}` - let x = cell % screen.window.width - let y = Math.floor(cell / screen.window.width) - return `((${y},${x})=${cell}:\\[bold]${hexcode}\\[]:F${fgText}:B${bgText}:A(${displayCellAttrs(cellAttrs)}))` - } - - const setFormattedText = (node, text) => { - node.innerHTML = '' - - let match - let attrs = {} - - let pushSpan = content => { - let span = mk('span') - node.appendChild(span) - span.textContent = content - for (let key in attrs) span[key] = attrs[key] - } - - while ((match = text.match(/\\\[(.*?)\]/))) { - if (match.index > 0) pushSpan(text.substr(0, match.index)) - - attrs = { style: '' } - let data = match[1].split(' ') - for (let attr of data) { - if (!attr) continue - let key, value - if (attr.indexOf('=') > -1) { - key = attr.substr(0, attr.indexOf('=')) - value = attr.substr(attr.indexOf('=') + 1) - } else { - key = attr - value = true - } - - if (key === 'bold') attrs.style += 'font-weight:bold;' - if (key === 'italic') attrs.style += 'font-style:italic;' - if (key === 'underline') attrs.style += 'text-decoration:underline;' - if (key === 'invert') attrs.style += 'background:#000;filter:invert(1);' - if (key === 'strike') attrs.style += 'text-decoration:line-through;' - if (key === 'overline') attrs.style += 'text-decoration:overline;' - if (key === 'faint') attrs.style += 'opacity:0.5;' - else if (key === 'color') attrs.style += `color:${value};` - else attrs[key] = value - } - - text = text.substr(match.index + match[0].length) - } - - if (text) pushSpan(text) - } - - let internalInfo = {} - - updateToolbar = () => { - if (!toolbarAttached) return - let text = `C((${screen.cursor.y},${screen.cursor.x}),hang:${screen.cursor.hanging},vis:${screen.cursor.visible})` - if (mouseHoverCell) { - text += ' m' + getCellData(mouseHoverCell[1] * screen.window.width + mouseHoverCell[0]) - } - setFormattedText(dataDisplay, text) - - if ('flags' in internalInfo) { - // we got ourselves some internal data - let text = ' ' - text += ` flags:${internalInfo.flags.toString(2)}` - text += ` curAttrs:${internalInfo.cursorAttrs.toString(2)}` - text += ` Region:${internalInfo.regionStart}->${internalInfo.regionEnd}` - text += ` Charset:${internalInfo.charsetGx} (0:${internalInfo.charsetG0},1:${internalInfo.charsetG1})` - text += ` Heap:${internalInfo.freeHeap}` - text += ` Clients:${internalInfo.clientCount}` - setFormattedText(internalDisplay, text) - } - } - - screen.on('draw', updateToolbar) - screen.on('internal', data => { - internalInfo = data - updateToolbar() - }) } diff --git a/js/term/screen_layout.js b/js/term/screen_layout.js index 357f670..1260a7a 100644 --- a/js/term/screen_layout.js +++ b/js/term/screen_layout.js @@ -247,6 +247,8 @@ module.exports = class ScreenLayout extends EventEmitter { this.renderer.resetDrawn() this.renderer.render('update-size', this.serializeRenderData()) + + this.emit('size-update') } } diff --git a/js/term/screen_renderer.js b/js/term/screen_renderer.js index 22af143..6c4910c 100644 --- a/js/term/screen_renderer.js +++ b/js/term/screen_renderer.js @@ -638,7 +638,6 @@ module.exports = class CanvasRenderer extends EventEmitter { if (y === height - 1) rectHeight += padding ctx.rect(rectX, rectY, rectWidth, rectHeight) - if (this.debug && this._debug) this._debug.clipRect(rectX, rectY, rectWidth, rectHeight) } ctx.save() @@ -673,7 +672,8 @@ module.exports = class CanvasRenderer extends EventEmitter { // set cell flags let flags = (+redrawMap.get(cell)) flags |= (+updateMap.get(cell)) << 1 - flags |= (+isTextWide(text)) << 2 + flags |= (+maskedCells.get(cell)) << 2 + flags |= (+isTextWide(text)) << 3 this._debug.setCell(cell, flags) } } diff --git a/sass/pages/_term.scss b/sass/pages/_term.scss index 3b2f54e..c9a8e44 100644 --- a/sass/pages/_term.scss +++ b/sass/pages/_term.scss @@ -106,8 +106,8 @@ body.term { .debug-canvas { position: absolute; - top: 6px; - left: 6px; + top: 0; + left: 0; pointer-events: none; }