Merge pull request from cwillisf/cache-gettimer

Flash 30 performance changes
This commit is contained in:
Chris Willis-Ford 2018-06-12 10:32:09 -07:00 committed by GitHub
commit 044a5e7f50
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 139 additions and 171 deletions

View file

@ -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
@ -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
//------------------------------

View file

@ -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

View file

@ -62,7 +62,6 @@ import extensions.ExtensionManager;
import flash.geom.Point;
import flash.utils.Dictionary;
import flash.utils.getTimer;
import primitives.*;
@ -70,10 +69,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;
@ -219,12 +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 {
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.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;
@ -247,9 +270,18 @@ public class Interpreter {
threads = newThreads;
if (threads.length == 0) return;
}
currentMSecs = getTimer();
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 {
@ -264,13 +296,15 @@ public class Interpreter {
}
}
yield = false;
var warpStartTimer:int = CachedTimer.getCachedTimer();
while (true) {
if (activeThread == warpThread) currentMSecs = getTimer();
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;
}

View file

@ -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;
}
}
}

View file

@ -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)

View file

@ -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;

View file

@ -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);

View file

@ -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();
}
}

View file

@ -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();
}

View file

@ -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 {

View file

@ -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);

55
src/util/CachedTimer.as Normal file
View file

@ -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;
}
}
}

View file

@ -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 {

View file

@ -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;
}
}}

View file

@ -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);

View file

@ -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];