From 02547f2a187ea1d5684e28f3959734fca6da4c9b Mon Sep 17 00:00:00 2001 From: Vanessa Freudenberg Date: Mon, 27 May 2024 17:58:19 -0700 Subject: [PATCH] release 1.2.1 --- index.html | 1 + squeak.js | 118 +++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 94 insertions(+), 25 deletions(-) diff --git a/index.html b/index.html index 075621b1..47ecf47a 100644 --- a/index.html +++ b/index.html @@ -98,6 +98,7 @@

About

News

+

May 2024Release 1.2.1 adds virtual cmd button, fixes touch events

March 2024 Release 1.2.0 adds FFI and MIDI plugins, JIT for Sista bytecodes, JPEG write prim, fixes keyboard input, copy/paste, scroll wheel, highdpi, allows ES6 in source

November 2023 Release 1.1.2 with more fixes (thanks to Agustin Martinez for narrowing down the problem).

November 2023 Expanded the High-performance JIT Mockups.

diff --git a/squeak.js b/squeak.js index 75dbfca0..4690e459 100644 --- a/squeak.js +++ b/squeak.js @@ -1409,7 +1409,11 @@ Objects are tenured to old space during a full GC. New objects are only referenced by other objects' pointers, and thus can be garbage-collected at any time by the Javascript GC. - A partial GC links new objects to support enumeration of new space. + A partial GC creates a linked list of new objects reachable from old space. We call this + list "young space". It is not stored, but only created by primitives like nextObject, + nextInstance, or become to support enumeration of new space. + To efficiently find potential young space roots, any write to an instance variable sets + the "dirty" flag of the object, allowing to skip clean objects. Weak references are finalized by a full GC. A partial GC only finalizes young weak references. @@ -1808,10 +1812,9 @@ // sorting them by id, and then linking them into old space. this.vm.addMessage("fullGC: " + reason); var start = Date.now(); - var previousNew = this.newSpaceCount; - var previousYoung = this.youngSpaceCount; + var previousNew = this.newSpaceCount; // includes young and newly allocated var previousOld = this.oldSpaceCount; - var newObjects = this.markReachableObjects(); + var newObjects = this.markReachableObjects(); // technically these are young objects this.removeUnmarkedOldObjects(); this.appendToOldObjects(newObjects); this.finalizeWeakReferences(); @@ -1821,11 +1824,20 @@ this.hasNewInstances = {}; this.gcCount++; this.gcMilliseconds += Date.now() - start; - var delta = previousOld - this.oldSpaceCount; - console.log("Full GC (" + reason + "): " + (Date.now() - start) + " ms, " + - "surviving objects: " + this.oldSpaceCount + " (" + this.oldSpaceBytes + " bytes), " + - "tenured " + newObjects.length + " (total " + (delta > 0 ? "+" : "") + delta + "), " + - "gc'ed " + previousYoung + " young and " + (previousNew - previousYoung) + " new objects"); + var delta = previousOld - this.oldSpaceCount; // absolute change + var survivingNew = newObjects.length; + var survivingOld = this.oldSpaceCount - survivingNew; + var gcedNew = previousNew - survivingNew; + var gcedOld = previousOld - survivingOld; + console.log("Full GC (" + reason + "): " + (Date.now() - start) + " ms;" + + " before: " + previousOld.toLocaleString() + " old objects;" + + " allocated " + previousNew.toLocaleString() + " new;" + + " surviving " + survivingOld.toLocaleString() + " old;" + + " tenuring " + survivingNew.toLocaleString() + " new;" + + " gc'ed " + gcedOld.toLocaleString() + " old and " + gcedNew.toLocaleString() + " new;" + + " total now: " + this.oldSpaceCount.toLocaleString() + " (" + (delta > 0 ? "+" : "") + delta.toLocaleString() + ", " + + this.oldSpaceBytes.toLocaleString() + " bytes)" + ); return newObjects.length > 0 ? newObjects[0] : null; }, @@ -1836,7 +1848,7 @@ }, markReachableObjects: function() { // FullGC: Visit all reachable objects and mark them. - // Return surviving new objects + // Return surviving new objects (young objects to be tenured). // Contexts are handled specially: they have garbage beyond the stack pointer // which must not be traced, and is cleared out here // In weak objects, only the inst vars are traced @@ -1986,8 +1998,8 @@ this.pgcCount++; this.pgcMilliseconds += Date.now() - start; console.log("Partial GC (" + reason+ "): " + (Date.now() - start) + " ms, " + - "found " + this.youngRootsCount + " roots in " + this.oldSpaceCount + " old, " + - "kept " + this.youngSpaceCount + " young (" + (previous - this.youngSpaceCount) + " gc'ed)"); + "found " + this.youngRootsCount.toLocaleString() + " roots in " + this.oldSpaceCount.toLocaleString() + " old, " + + "kept " + this.youngSpaceCount.toLocaleString() + " young (" + (previous - this.youngSpaceCount).toLocaleString() + " gc'ed)"); return young[0]; }, youngRoots: function() { @@ -6711,8 +6723,16 @@ return this.popNandPushIfOK(argCount+1, this.makeLargeIfNeeded(bytes)); }, primitivePartialGC: function(argCount) { - this.vm.image.partialGC("primitive"); - var bytes = this.vm.image.bytesLeft(); + var young = this.vm.image.partialGC("primitive"); + var youngSpaceBytes = 0; + while (young) { + youngSpaceBytes += young.totalBytes(); + young = young.nextObject; + } + console.log(" old space: " + this.vm.image.oldSpaceBytes.toLocaleString() + " bytes, " + + "young space: " + youngSpaceBytes.toLocaleString() + " bytes, " + + "total: " + (this.vm.image.oldSpaceBytes + youngSpaceBytes).toLocaleString() + " bytes"); + var bytes = this.vm.image.bytesLeft() - youngSpaceBytes; return this.popNandPushIfOK(argCount+1, this.makeLargeIfNeeded(bytes)); }, primitiveMakePoint: function(argCount, checkNumbers) { @@ -56181,7 +56201,8 @@ function recordModifiers(evt, display) { var shiftPressed = evt.shiftKey, ctrlPressed = evt.ctrlKey && !evt.altKey, - cmdPressed = display.isMac ? evt.metaKey : evt.altKey && !evt.ctrlKey, + cmdPressed = (display.isMac ? evt.metaKey : evt.altKey && !evt.ctrlKey) + || display.cmdButtonTouched, modifiers = (shiftPressed ? Squeak.Keyboard_Shift : 0) + (ctrlPressed ? Squeak.Keyboard_Ctrl : 0) + @@ -56190,9 +56211,15 @@ return modifiers; } - var canUseMouseOffset = navigator.userAgent.match("AppleWebKit/"); + var canUseMouseOffset = null; function updateMousePos(evt, canvas, display) { + if (canUseMouseOffset === null) { + // Per https://caniuse.com/mdn-api_mouseevent_offsetx, essentially all *current* + // browsers support `offsetX`/`offsetY`, but it does little harm to fall back to the + // older `layerX`/`layerY` for now. + canUseMouseOffset = 'offsetX' in evt; + } var evtX = canUseMouseOffset ? evt.offsetX : evt.layerX, evtY = canUseMouseOffset ? evt.offsetY : evt.layerY; if (display.cursorCanvas) { @@ -56373,6 +56400,7 @@ mouseY: 0, buttons: 0, keys: [], + cmdButtonTouched: null, // touchscreen button pressed (touch ID) eventQueue: null, // only used if image uses event primitives clipboardString: '', clipboardStringChanged: false, @@ -56523,10 +56551,18 @@ function touchToMouse(evt) { if (evt.touches.length) { // average all touch positions + // but ignore the cmd button touch touch.x = touch.y = 0; + let n = 0; for (var i = 0; i < evt.touches.length; i++) { - touch.x += evt.touches[i].pageX / evt.touches.length; - touch.y += evt.touches[i].pageY / evt.touches.length; + if (evt.touches[i].identifier === display.cmdButtonTouched) continue; + touch.x += evt.touches[i].pageX; + touch.y += evt.touches[i].pageY; + n++; + } + if (n > 0) { + touch.x /= n; + touch.y /= n; } } return { @@ -56632,12 +56668,14 @@ // * if fingers moved significantly within 200ms of 2nd down, start zooming // * if touch ended within this time, generate click (down+up) // * otherwise, start mousing with 2nd button + // * also, ignore finger on cmd button // When mousing, always generate a move event before down event so that // mouseover eventhandlers in image work better canvas.ontouchstart = function(evt) { evt.preventDefault(); var e = touchToMouse(evt); for (var i = 0; i < evt.changedTouches.length; i++) { + if (evt.changedTouches[i].identifier === display.cmdButtonTouched) continue; switch (touch.state) { case 'idle': touch.state = 'got1stFinger'; @@ -56682,14 +56720,14 @@ break; case 'mousing': recordMouseEvent('mousemove', e, canvas, display, options); - return; + break; case 'got2ndFinger': if (evt.touches.length > 1) touch.dist = dist(evt.touches[0], evt.touches[1]); - return; + break; case 'zooming': zoomMove(evt); - return; + break; } }; canvas.ontouchend = function(evt) { @@ -56697,19 +56735,20 @@ checkFullscreen(); var e = touchToMouse(evt); for (var i = 0; i < evt.changedTouches.length; i++) { + if (evt.changedTouches[i].identifier === display.cmdButtonTouched) continue; switch (touch.state) { case 'mousing': if (evt.touches.length > 0) break; touch.state = 'idle'; recordMouseEvent('mouseup', e, canvas, display, options); - return; + break; case 'got1stFinger': touch.state = 'idle'; touch.button = e.button = 0; recordMouseEvent('mousemove', e, canvas, display, options); recordMouseEvent('mousedown', e, canvas, display, options); recordMouseEvent('mouseup', e, canvas, display, options); - return; + break; case 'got2ndFinger': touch.state = 'mousing'; touch.button = e.button = 2; @@ -56720,7 +56759,7 @@ if (evt.touches.length > 0) break; touch.state = 'idle'; zoomEnd(); - return; + break; } } }; @@ -56752,8 +56791,9 @@ input.style.opacity = "0"; input.style.pointerEvents = "none"; canvas.parentElement.appendChild(input); - // touch-keyboard button + // touch-keyboard buttons if ('ontouchstart' in document) { + // button to show on-screen keyboard var keyboardButton = document.createElement('div'); keyboardButton.innerHTML = ''; keyboardButton.setAttribute('style', 'position:fixed;right:0;bottom:0;background-color:rgba(128,128,128,0.5);border-radius:5px'); @@ -56764,6 +56804,34 @@ evt.preventDefault(); }; keyboardButton.ontouchstart = keyboardButton.onmousedown; + // modifier button for CMD key + var cmdButton = document.createElement('div'); + cmdButton.innerHTML = '⌘'; + cmdButton.setAttribute('style', 'position:fixed;left:0;background-color:rgba(128,128,128,0.5);width:50px;height:50px;font-size:30px;text-align:center;vertical-align:middle;line-height:50px;border-radius:5px'); + if (window.visualViewport) { + // fix position of button when virtual keyboard is shown + const vv = window.visualViewport; + const fixPosition = () => cmdButton.style.top = `${vv.height}px`; + vv.addEventListener('resize', fixPosition); + cmdButton.style.transform = `translateY(-100%)`; + fixPosition(); + } else { + cmdButton.style.bottom = '0'; + } + canvas.parentElement.appendChild(cmdButton); + cmdButton.ontouchstart = function(evt) { + display.cmdButtonTouched = evt.changedTouches[0].identifier; + cmdButton.style.backgroundColor = 'rgba(255,255,255,0.5)'; + evt.preventDefault(); + evt.stopPropagation(); + }; + cmdButton.ontouchend = function(evt) { + display.cmdButtonTouched = null; + cmdButton.style.backgroundColor = 'rgba(128,128,128,0.5)'; + evt.preventDefault(); + evt.stopPropagation(); + }; + cmdButton.ontouchcancel = cmdButton.ontouchend; } else { // keep focus on input field input.onblur = function() { input.focus(); };