From fa059c9d1e80460990ed94874db2eeb1a5deb8d6 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <cwillisf@media.mit.edu> Date: Fri, 8 Jun 2018 12:38:10 -0700 Subject: [PATCH 1/4] Add CachedTimer, replace getTimer() calls --- src/Scratch.as | 75 +--------------------- src/extensions/ExtensionManager.as | 4 +- src/interpreter/Interpreter.as | 13 ++-- src/logging/LogEntry.as | 6 +- src/render3d/DisplayObjectContainerIn3D.as | 5 -- src/scratch/ScratchObj.as | 2 +- src/scratch/ScratchRuntime.as | 10 +-- src/sound/ScratchSoundPlayer.as | 6 +- src/ui/parts/LibraryPart.as | 6 +- src/ui/parts/ScriptsPart.as | 6 +- src/uiwidgets/Menu.as | 6 +- src/util/CachedTimer.as | 55 ++++++++++++++++ src/util/GestureHandler.as | 12 ++-- src/util/Perf.as | 55 ---------------- src/util/Transition.as | 4 +- src/watchers/ListWatcher.as | 8 ++- 16 files changed, 104 insertions(+), 169 deletions(-) create mode 100644 src/util/CachedTimer.as delete mode 100644 src/util/Perf.as diff --git a/src/Scratch.as b/src/Scratch.as index 3b03154..b8bd666 100644 --- a/src/Scratch.as +++ b/src/Scratch.as @@ -734,6 +734,7 @@ public class Scratch extends Sprite { protected function step(e:Event):void { // Step the runtime system and all UI components. + CachedTimer.clearCachedTimer(); gh.step(); runtime.stepRuntime(); Transition.step(null); @@ -971,10 +972,6 @@ public class Scratch extends Sprite { scriptsPart.setWidthHeight(contentW, contentH); if (mediaLibrary) mediaLibrary.setWidthHeight(topBarPart.w, fullH); - if (frameRateGraph) { - frameRateGraph.y = stage.stageHeight - frameRateGraphH; - addChild(frameRateGraph); // put in front - } SCRATCH::allow3d { if (isIn3D) render3D.onStageResize(); @@ -1497,76 +1494,6 @@ public class Scratch extends Sprite { lp.y = int(p.y + ((stagePane.height - lp.height) / 2)); } - // ----------------------------- - // Frame rate readout (for use during development) - //------------------------------ - - private var frameRateReadout:TextField; - private var firstFrameTime:int; - private var frameCount:int; - - protected function addFrameRateReadout(x:int, y:int, color:uint = 0):void { - frameRateReadout = new TextField(); - frameRateReadout.autoSize = TextFieldAutoSize.LEFT; - frameRateReadout.selectable = false; - frameRateReadout.background = false; - frameRateReadout.defaultTextFormat = new TextFormat(CSS.font, 12, color); - frameRateReadout.x = x; - frameRateReadout.y = y; - addChild(frameRateReadout); - frameRateReadout.addEventListener(Event.ENTER_FRAME, updateFrameRate); - } - - private function updateFrameRate(e:Event):void { - frameCount++; - if (!frameRateReadout) return; - var now:int = getTimer(); - var msecs:int = now - firstFrameTime; - if (msecs > 500) { - var fps:Number = Math.round((1000 * frameCount) / msecs); - frameRateReadout.text = fps + ' fps (' + Math.round(msecs / frameCount) + ' msecs)'; - firstFrameTime = now; - frameCount = 0; - } - } - - // TODO: Remove / no longer used - private const frameRateGraphH:int = 150; - private var frameRateGraph:Shape; - private var nextFrameRateX:int; - private var lastFrameTime:int; - - private function addFrameRateGraph():void { - addChild(frameRateGraph = new Shape()); - frameRateGraph.y = stage.stageHeight - frameRateGraphH; - clearFrameRateGraph(); - stage.addEventListener(Event.ENTER_FRAME, updateFrameRateGraph); - } - - public function clearFrameRateGraph():void { - var g:Graphics = frameRateGraph.graphics; - g.clear(); - g.beginFill(0xFFFFFF); - g.drawRect(0, 0, stage.stageWidth, frameRateGraphH); - nextFrameRateX = 0; - } - - private function updateFrameRateGraph(evt:*):void { - var now:int = getTimer(); - var msecs:int = now - lastFrameTime; - lastFrameTime = now; - var c:int = 0x505050; - if (msecs > 40) c = 0xE0E020; - if (msecs > 50) c = 0xA02020; - - if (nextFrameRateX > stage.stageWidth) clearFrameRateGraph(); - var g:Graphics = frameRateGraph.graphics; - g.beginFill(c); - var barH:int = Math.min(frameRateGraphH, msecs / 2); - g.drawRect(nextFrameRateX, frameRateGraphH - barH, 1, barH); - nextFrameRateX++; - } - // ----------------------------- // Camera Dialog //------------------------------ diff --git a/src/extensions/ExtensionManager.as b/src/extensions/ExtensionManager.as index 488ea58..5a38fa5 100644 --- a/src/extensions/ExtensionManager.as +++ b/src/extensions/ExtensionManager.as @@ -403,7 +403,7 @@ public class ExtensionManager { public function updateIndicator(indicator:IndicatorLight, ext:ScratchExtension, firstTime:Boolean = false):void { if(ext.port > 0) { - var msecsSinceLastResponse:uint = getTimer() - ext.lastPollResponseTime; + var msecsSinceLastResponse:uint = CachedTimer.getCachedTimer() - ext.lastPollResponseTime; if (msecsSinceLastResponse > 500) indicator.setColorAndMsg(0xE00000, 'Cannot find helper app'); else if (ext.problem != '') indicator.setColorAndMsg(0xE0E000, ext.problem); else indicator.setColorAndMsg(0x00C000, ext.success); @@ -724,7 +724,7 @@ public class ExtensionManager { private function processPollResponse(ext:ScratchExtension, response:String):void { if (response == null) return; - ext.lastPollResponseTime = getTimer(); + ext.lastPollResponseTime = CachedTimer.getCachedTimer(); ext.problem = ''; // clear the busy list unless we just started a command that waits diff --git a/src/interpreter/Interpreter.as b/src/interpreter/Interpreter.as index 480f359..e27435c 100644 --- a/src/interpreter/Interpreter.as +++ b/src/interpreter/Interpreter.as @@ -70,10 +70,12 @@ import scratch.*; import sound.*; +import util.CachedTimer; + public class Interpreter { - public var activeThread:Thread; // current thread - public var currentMSecs:int = getTimer(); // millisecond clock for the current step + public var activeThread:Thread; // current thread + public var currentMSecs:int; // millisecond clock for the current step public var turboMode:Boolean = false; private var app:Scratch; @@ -220,10 +222,9 @@ public class Interpreter { } public function stepThreads():void { - startTime = getTimer(); var workTime:int = (0.75 * 1000) / app.stage.frameRate; // work for up to 75% of one frame time doRedraw = false; - currentMSecs = getTimer(); + startTime = currentMSecs = CachedTimer.getCachedTimer(); if (threads.length == 0) return; while ((currentMSecs - startTime) < workTime) { if (warpThread && (warpThread.block == null)) clearWarpBlock(); @@ -247,7 +248,7 @@ public class Interpreter { threads = newThreads; if (threads.length == 0) return; } - currentMSecs = getTimer(); + currentMSecs = CachedTimer.getFreshTimer(); if (doRedraw || (runnableCount == 0)) return; } } @@ -265,7 +266,7 @@ public class Interpreter { } yield = false; while (true) { - if (activeThread == warpThread) currentMSecs = getTimer(); + if (activeThread == warpThread) currentMSecs = CachedTimer.getFreshTimer(); evalCmd(activeThread.block); if (yield) { if (activeThread == warpThread) { diff --git a/src/logging/LogEntry.as b/src/logging/LogEntry.as index 22940b4..0d9e173 100644 --- a/src/logging/LogEntry.as +++ b/src/logging/LogEntry.as @@ -20,6 +20,8 @@ package logging { import flash.utils.getTimer; +import util.CachedTimer; + public class LogEntry { public var timeStamp:Number; public var severity:int; @@ -49,11 +51,11 @@ public class LogEntry { return [makeTimeStampString(), LogLevel.LEVEL[severity], messageKey].join(' | '); } - private static const timerOffset:Number = new Date().time - getTimer(); + private static const timerOffset:Number = new Date().time - CachedTimer.getFreshTimer(); // Returns approximately the same value as "new Date().time" without GC impact public static function getCurrentTime():Number { - return getTimer() + timerOffset; + return CachedTimer.getCachedTimer() + timerOffset; } } } diff --git a/src/render3d/DisplayObjectContainerIn3D.as b/src/render3d/DisplayObjectContainerIn3D.as index 3571583..53302a2 100644 --- a/src/render3d/DisplayObjectContainerIn3D.as +++ b/src/render3d/DisplayObjectContainerIn3D.as @@ -1052,12 +1052,8 @@ SCRATCH::allow3d{ currentTexture = null; } - private var drawCount:uint = 0; - //private var lastTime:int = 0; public function onRender(e:Event):void { if (!scratchStage) return; - //trace('frame was '+(getTimer() - lastTime)+'ms.'); - //lastTime = getTimer(); if (scratchStage.stage.stage3Ds[0] == null || __context == null || __context.driverInfo == "Disposed") { if (__context) __context.dispose(); @@ -1068,7 +1064,6 @@ SCRATCH::allow3d{ draw(); __context.present(); - ++drawCount; // Invalidate cached renders for (var o:Object in cachedOtherRenderBitmaps) diff --git a/src/scratch/ScratchObj.as b/src/scratch/ScratchObj.as index a6fe8ac..419b74d 100644 --- a/src/scratch/ScratchObj.as +++ b/src/scratch/ScratchObj.as @@ -544,7 +544,7 @@ public class ScratchObj extends Sprite { public function click(evt:MouseEvent):void { var app:Scratch = root as Scratch; if (!app) return; - var now:uint = getTimer(); + var now:uint = CachedTimer.getCachedTimer(); app.runtime.startClickedHats(this); if ((now - lastClickTime) < DOUBLE_CLICK_MSECS) { if (isStage || ScratchSprite(this).isClone) return; diff --git a/src/scratch/ScratchRuntime.as b/src/scratch/ScratchRuntime.as index 51c4534..2d85b9a 100644 --- a/src/scratch/ScratchRuntime.as +++ b/src/scratch/ScratchRuntime.as @@ -108,7 +108,7 @@ public class ScratchRuntime { return; } if (ready==ReadyLabel.COUNTDOWN) { - var tR:Number = getTimer()*.001-videoSeconds; + var tR:Number = CachedTimer.getCachedTimer()*.001-videoSeconds; while (t>videoSounds.length/videoFramerate+1/videoFramerate) { saveSound(); } @@ -135,7 +135,7 @@ public class ScratchRuntime { } } if (recording) { // Recording a YouTube video? - var t:Number = getTimer()*.001-videoSeconds; + var t:Number = CachedTimer.getCachedTimer()*.001-videoSeconds; //If, based on time and framerate, the current frame needs to be in the video, capture the frame. //Will always be true if framerate is 30, as every frame is captured. if (t>videoSounds.length/videoFramerate+1/videoFramerate) { @@ -213,7 +213,7 @@ public class ScratchRuntime { private function saveFrame():void { saveSound(); - var t:Number = getTimer()*.001-videoSeconds; + var t:Number = CachedTimer.getCachedTimer()*.001-videoSeconds; while (t>videoSounds.length/videoFramerate+1/videoFramerate) { saveSound(); } @@ -380,7 +380,7 @@ public class ScratchRuntime { videoHeight = 360; } ready=ReadyLabel.COUNTDOWN; - videoSeconds = getTimer()*.001; + videoSeconds = CachedTimer.getCachedTimer()*.001; baFlvEncoder = new ByteArrayFlvEncoder(videoFramerate); baFlvEncoder.setVideoProperties(videoWidth, videoHeight); baFlvEncoder.setAudioProperties(FlvEncoder.SAMPLERATE_44KHZ, true, true, true); @@ -433,7 +433,7 @@ public class ScratchRuntime { ready=ReadyLabel.NOT_READY; app.refreshStagePart(); var player:ScratchSoundPlayer, length:int; - videoSeconds = getTimer() * 0.001; + videoSeconds = CachedTimer.getCachedTimer() * 0.001; for each (player in ScratchSoundPlayer.activeSounds) { length = int((player.soundChannel.position*.001)*videoFramerate); player.readPosition = Math.max(Math.min(baFlvEncoder.audioFrameSize*length,player.dataBytes.length),0); diff --git a/src/sound/ScratchSoundPlayer.as b/src/sound/ScratchSoundPlayer.as index b0573f9..5a466c0 100644 --- a/src/sound/ScratchSoundPlayer.as +++ b/src/sound/ScratchSoundPlayer.as @@ -33,6 +33,8 @@ import flash.utils.ByteArray; import scratch.ScratchSound; +import util.CachedTimer; + public class ScratchSoundPlayer { static public var activeSounds:Array = []; @@ -160,7 +162,7 @@ public class ScratchSoundPlayer { private function writeSampleData(evt:SampleDataEvent):void { var i:int; - if ((lastBufferTime != 0) && ((getTimer() - lastBufferTime) > 230)) { + if ((lastBufferTime != 0) && ((CachedTimer.getCachedTimer() - lastBufferTime) > 230)) { soundChannel = null; // don't explicitly stop the sound channel in this callback; allow it to stop on its own stopPlaying(); return; @@ -174,7 +176,7 @@ public class ScratchSoundPlayer { } dataBytes.writeBytes(data); if ((bytePosition >= endOffset) && (lastBufferTime == 0)) { - lastBufferTime = getTimer(); + lastBufferTime = CachedTimer.getCachedTimer(); } } diff --git a/src/ui/parts/LibraryPart.as b/src/ui/parts/LibraryPart.as index 9961bf3..aa29e4d 100644 --- a/src/ui/parts/LibraryPart.as +++ b/src/ui/parts/LibraryPart.as @@ -32,6 +32,8 @@ import ui.media.*; import ui.SpriteThumbnail; import uiwidgets.*; +import util.CachedTimer; + public class LibraryPart extends UIPart { private const smallTextFormat:TextFormat = new TextFormat(CSS.font, 10, CSS.textColor); @@ -251,12 +253,12 @@ public class LibraryPart extends UIPart { public function step():void { // Update thumbnails and sprite details. var viewedObj:ScratchObj = app.viewedObj(); - var updateThumbnails:Boolean = ((getTimer() - lastUpdate) > updateInterval); + var updateThumbnails:Boolean = ((CachedTimer.getCachedTimer() - lastUpdate) > updateInterval); for each (var tn:SpriteThumbnail in allThumbnails()) { if (updateThumbnails) tn.updateThumbnail(); tn.select(tn.targetObj == viewedObj); } - if (updateThumbnails) lastUpdate = getTimer(); + if (updateThumbnails) lastUpdate = CachedTimer.getCachedTimer(); if (spriteDetails.visible) spriteDetails.step(); if (videoButton && videoButton.visible) updateVideoButton(); } diff --git a/src/ui/parts/ScriptsPart.as b/src/ui/parts/ScriptsPart.as index cf707f1..7e621f9 100644 --- a/src/ui/parts/ScriptsPart.as +++ b/src/ui/parts/ScriptsPart.as @@ -33,6 +33,8 @@ import ui.*; import uiwidgets.*; +import util.CachedTimer; + public class ScriptsPart extends UIPart { private var shape:Shape; @@ -130,12 +132,12 @@ public class ScriptsPart extends UIPart { private var lastUpdateTime:uint; private function updateExtensionIndicators():void { - if ((getTimer() - lastUpdateTime) < 500) return; + if ((CachedTimer.getCachedTimer() - lastUpdateTime) < 500) return; for (var i:int = 0; i < app.palette.numChildren; i++) { var indicator:IndicatorLight = app.palette.getChildAt(i) as IndicatorLight; if (indicator) app.extensionManager.updateIndicator(indicator, indicator.target); } - lastUpdateTime = getTimer(); + lastUpdateTime = CachedTimer.getCachedTimer(); } public function setWidthHeight(w:int, h:int):void { diff --git a/src/uiwidgets/Menu.as b/src/uiwidgets/Menu.as index 2069dea..418fde2 100644 --- a/src/uiwidgets/Menu.as +++ b/src/uiwidgets/Menu.as @@ -34,6 +34,8 @@ package uiwidgets { import flash.utils.getTimer; import translation.TranslatableStrings; +import util.CachedTimer; + public class Menu extends Sprite { // when stringCollectionMode is true menus are not displayed but strings are recorded for translation @@ -192,8 +194,8 @@ public class Menu extends Sprite { return; } - if ((getTimer() - lastTime) < scrollMSecs) return; - lastTime = getTimer(); + if ((CachedTimer.getCachedTimer() - lastTime) < scrollMSecs) return; + lastTime = CachedTimer.getCachedTimer(); var localY:int = this.globalToLocal(new Point(stage.mouseX, stage.mouseY)).y; if ((localY < (2 + scrollInset)) && (firstItemIndex > 0)) scrollBy(-1); diff --git a/src/util/CachedTimer.as b/src/util/CachedTimer.as new file mode 100644 index 0000000..fd41811 --- /dev/null +++ b/src/util/CachedTimer.as @@ -0,0 +1,55 @@ +/* + * Scratch Project Editor and Player + * Copyright (C) 2018 Massachusetts Institute of Technology + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package util { +import flash.utils.getTimer; + +/** + * Calling getTimer() is much more expensive in Flash 30 than in previous versions. + * This class is meant to reduce the number of actual calls to getTimer() with minimal changes to existing code. + */ +public class CachedTimer { + private static var dirty:Boolean = true; + private static var cachedTimer:int; + + /** + * @return the last cached value of getTimer(). May return a fresh value if the cache has been invalidated. + */ + public static function getCachedTimer():int { + return dirty ? getFreshTimer() : cachedTimer; + } + + /** + * Clear the timer cache, forcing getCachedTimer() to get a fresh value next time. Use this at the top of a frame. + */ + public static function clearCachedTimer():void { + dirty = true; + } + + /** + * @return and cache the current value of getTimer(). + * Use this if you need an accurate timer value in the middle of a frame. + */ + public static function getFreshTimer():int { + cachedTimer = getTimer(); + dirty = false; + return cachedTimer; + } +} +} diff --git a/src/util/GestureHandler.as b/src/util/GestureHandler.as index 8b95ab5..e5526a0 100644 --- a/src/util/GestureHandler.as +++ b/src/util/GestureHandler.as @@ -123,7 +123,7 @@ public class GestureHandler { } public function step():void { - if ((getTimer() - mouseDownTime) > DOUBLE_CLICK_MSECS) { + if ((CachedTimer.getCachedTimer() - mouseDownTime) > DOUBLE_CLICK_MSECS) { if (gesture == "unknown") { if (mouseTarget != null) handleDrag(null); if (gesture != 'drag') handleClick(mouseDownEvent); @@ -132,7 +132,7 @@ public class GestureHandler { handleClick(mouseDownEvent); } } - if (carriedObj && scrollTarget && (getTimer() - scrollStartTime) > SCROLL_MSECS && (scrollXVelocity || scrollYVelocity)) { + if (carriedObj && scrollTarget && (CachedTimer.getCachedTimer() - scrollStartTime) > SCROLL_MSECS && (scrollXVelocity || scrollYVelocity)) { if (scrollTarget.allowHorizontalScrollbar) { scrollTarget.contents.x = Math.min(0, Math.max(-scrollTarget.maxScrollH(), scrollTarget.contents.x + scrollXVelocity)); } @@ -195,7 +195,7 @@ public class GestureHandler { handleTool(evt); return; } - mouseDownTime = getTimer(); + mouseDownTime = CachedTimer.getCachedTimer(); mouseDownEvent = evt; gesture = "unknown"; mouseTarget = null; @@ -261,7 +261,7 @@ public class GestureHandler { if (t is ScrollFrameContents) { scrollTarget = t.parent as ScrollFrame; if (scrollTarget != oldTarget) { - scrollStartTime = getTimer(); + scrollStartTime = CachedTimer.getCachedTimer(); } break; } @@ -293,7 +293,7 @@ public class GestureHandler { } } if (!scrollXVelocity && !scrollYVelocity) { - scrollStartTime = getTimer(); + scrollStartTime = CachedTimer.getCachedTimer(); } } if (bubble) { @@ -540,7 +540,7 @@ public class GestureHandler { obj.startDrag(); if(obj is DisplayObject) obj.cacheAsBitmap = true; carriedObj = obj; - scrollStartTime = getTimer(); + scrollStartTime = CachedTimer.getCachedTimer(); } private function dropHandled(droppedObj:*, evt:MouseEvent):Boolean { diff --git a/src/util/Perf.as b/src/util/Perf.as deleted file mode 100644 index 27fb553..0000000 --- a/src/util/Perf.as +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Scratch Project Editor and Player - * Copyright (C) 2014 Massachusetts Institute of Technology - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package util { -import flash.utils.getTimer; - -public class Perf { - - private static var totalStart:uint; - private static var lapStart:uint; - private static var lapTotal:uint; - - public static function start(msg:String = null):void { - if (!msg) msg = 'Perf.start'; - Scratch.app.log(msg); - totalStart = lapStart = getTimer(); - lapTotal = 0; - } - - public static function clearLap():void { - lapStart = getTimer(); - } - - public static function lap(msg:String = ""):void { - if (totalStart == 0) return; // not monitoring performance - var lapMSecs:uint = getTimer() - lapStart; - Scratch.app.log(' ' + msg + ': ' + lapMSecs + ' msecs'); - lapTotal += lapMSecs; - lapStart = getTimer(); - } - - public static function end():void { - if (totalStart == 0) return; // not monitoring performance - var totalMSecs:uint = getTimer() - totalStart; - var unaccountedFor:uint = totalMSecs - lapTotal; - Scratch.app.log('Total: ' + totalMSecs + ' msecs; unaccounted for: ' + unaccountedFor + ' msecs (' + int((100 * unaccountedFor) / totalMSecs) + '%)'); - totalStart = lapStart = lapTotal = 0; - } -}} diff --git a/src/util/Transition.as b/src/util/Transition.as index 408ff36..bbe3edc 100644 --- a/src/util/Transition.as +++ b/src/util/Transition.as @@ -48,7 +48,7 @@ public class Transition { } else { delta = endValue - startValue; } - startMSecs = getTimer(); + startMSecs = CachedTimer.getCachedTimer(); duration = 1000 * secs; } @@ -66,7 +66,7 @@ public class Transition { public static function step(evt:*):void { if (activeTransitions.length == 0) return; - var now:uint = getTimer(); + var now:uint = CachedTimer.getCachedTimer(); var newActive:Array = []; for each (var t:Transition in activeTransitions) { if (t.apply(now)) newActive.push(t); diff --git a/src/watchers/ListWatcher.as b/src/watchers/ListWatcher.as index 4ca4f92..7bb4af0 100644 --- a/src/watchers/ListWatcher.as +++ b/src/watchers/ListWatcher.as @@ -26,7 +26,9 @@ package watchers { import interpreter.Interpreter; import scratch.ScratchObj; import translation.Translator; - import util.JSON; + +import util.CachedTimer; +import util.JSON; import uiwidgets.*; public class ListWatcher extends Sprite { @@ -222,7 +224,7 @@ public class ListWatcher extends Sprite { if (!visible) return; adjustLastAccessSize(); if ((i < 1) || (i > lastAccess.length)) return; - lastAccess[i - 1] = getTimer(); + lastAccess[i - 1] = CachedTimer.getCachedTimer(); lastActiveIndex = i - 1; interp.redraw(); } @@ -265,7 +267,7 @@ public class ListWatcher extends Sprite { // Highlight the cell number of all recently accessed cells currently visible. const fadeoutMSecs:int = 800; adjustLastAccessSize(); - var now:int = getTimer(); + var now:int = CachedTimer.getCachedTimer(); isIdle = true; // try to be idle; set to false if any non-zero lastAccess value is found for (var i:int = 0; i < visibleCellNums.length; i++) { var lastAccessTime:int = lastAccess[firstVisibleIndex + i]; From b0de4bb377d8586dfff7ac22b7468c7a3a8f5f5c Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <cwillisf@media.mit.edu> Date: Fri, 8 Jun 2018 15:29:23 -0700 Subject: [PATCH 2/4] Use cached timer more often when warping --- src/interpreter/Interpreter.as | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/interpreter/Interpreter.as b/src/interpreter/Interpreter.as index e27435c..791a65e 100644 --- a/src/interpreter/Interpreter.as +++ b/src/interpreter/Interpreter.as @@ -224,7 +224,7 @@ public class Interpreter { public function stepThreads():void { var workTime:int = (0.75 * 1000) / app.stage.frameRate; // work for up to 75% of one frame time doRedraw = false; - startTime = currentMSecs = CachedTimer.getCachedTimer(); + startTime = currentMSecs = CachedTimer.getFreshTimer(); if (threads.length == 0) return; while ((currentMSecs - startTime) < workTime) { if (warpThread && (warpThread.block == null)) clearWarpBlock(); @@ -265,13 +265,15 @@ public class Interpreter { } } yield = false; + var warpStartTimer:int = CachedTimer.getCachedTimer(); while (true) { - if (activeThread == warpThread) currentMSecs = CachedTimer.getFreshTimer(); + if (activeThread == warpThread) currentMSecs = warpStartTimer; evalCmd(activeThread.block); if (yield) { if (activeThread == warpThread) { if ((currentMSecs - startTime) > warpMSecs) return; yield = false; + warpStartTimer = CachedTimer.getFreshTimer(); continue; } else return; } From 0c605cabd3c511457108d08215cedb4c53581990 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <cwillisf@media.mit.edu> Date: Fri, 8 Jun 2018 17:05:54 -0700 Subject: [PATCH 3/4] Minimize how often we check against workTime --- src/interpreter/Interpreter.as | 35 ++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/interpreter/Interpreter.as b/src/interpreter/Interpreter.as index 791a65e..a515d8f 100644 --- a/src/interpreter/Interpreter.as +++ b/src/interpreter/Interpreter.as @@ -62,7 +62,6 @@ import extensions.ExtensionManager; import flash.geom.Point; import flash.utils.Dictionary; -import flash.utils.getTimer; import primitives.*; @@ -221,11 +220,34 @@ public class Interpreter { doRedraw = true; } + private const workTimeCheckIntervalFactor:Number = 1/3.0; + private const maxIterationCountSamples: uint = 10; + private var iterationCountSamples: Vector.<uint> = new <uint>[500]; // initial guess + + private function addIterationCountSample(sample:uint):void { + iterationCountSamples.push(sample); + while (iterationCountSamples.length > maxIterationCountSamples) { + iterationCountSamples.shift(); + } + } + + private function getAverageIterationCount():Number { + var total:uint = 0; + for each (var sample:uint in iterationCountSamples) { + total += sample; + } + return Number(total) / iterationCountSamples.length; + } + public function stepThreads():void { var workTime:int = (0.75 * 1000) / app.stage.frameRate; // work for up to 75% of one frame time doRedraw = false; startTime = currentMSecs = CachedTimer.getFreshTimer(); if (threads.length == 0) return; + var currentEstimate:Number = getAverageIterationCount(); + var iterationCount:uint = 0; + var checkInterval:uint = Math.round(workTimeCheckIntervalFactor * currentEstimate); + var checkCount:uint = 0; while ((currentMSecs - startTime) < workTime) { if (warpThread && (warpThread.block == null)) clearWarpBlock(); var threadStopped:Boolean = false; @@ -248,9 +270,18 @@ public class Interpreter { threads = newThreads; if (threads.length == 0) return; } - currentMSecs = CachedTimer.getFreshTimer(); if (doRedraw || (runnableCount == 0)) return; + ++iterationCount; + ++checkCount; + if (checkCount >= checkInterval) { + currentMSecs = CachedTimer.getFreshTimer(); + checkCount = 0; + } } + // if we get here, this was a frame where we needed to check the timer twice or more + // use the elapsed time and actual iteration count to generate an estimate for iterations per step + var newEstimate:uint = Math.round(workTime * iterationCount / Number(currentMSecs - startTime)); + addIterationCountSample(newEstimate); } private function stepActiveThread():void { From 825cbcc0cbab3f1e7f801a68ad77419ea73ed9e3 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford <cwillisf@media.mit.edu> Date: Mon, 11 Jun 2018 10:26:45 -0700 Subject: [PATCH 4/4] v461 version bump: Flash 30 performance changes --- src/Scratch.as | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Scratch.as b/src/Scratch.as index b8bd666..b6947ca 100644 --- a/src/Scratch.as +++ b/src/Scratch.as @@ -75,7 +75,7 @@ import watchers.ListWatcher; public class Scratch extends Sprite { // Version - public static const versionString:String = 'v460.0.1'; + public static const versionString:String = 'v461'; public static var app:Scratch; // static reference to the app, used for debugging // Display modes