Visible Stepping, and more

* Visible Stepping with flashing blocks + slider for stepping speed
* user-controlled visible stepping with “step forward” button
* speed-up: removed unnecessary yields when running custom blocks
* you can now force a single yield with “wait 0” block
* tweaked keyboard entry: if inserted block has inputs go to first one
* only autoscroll panes where it makes sense (not the palette etc.)
* populate messages drop-down with all msgs, including ones from custom
block definition
dev
jmoenig 2016-10-15 11:15:01 +02:00
rodzic 7769bffd63
commit ff5f9a7601
8 zmienionych plików z 477 dodań i 53 usunięć

129
blocks.js
Wyświetl plik

@ -145,11 +145,11 @@ radians, useBlurredShadows, SpeechBubbleMorph, modules, StageMorph,
fontHeight, TableFrameMorph, SpriteMorph, Context, ListWatcherMorph,
CellMorph, DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph,
Costume, IDE_Morph, BlockDialogMorph, BlockEditorMorph, localize, isNil,
isSnapObject, copy, PushButtonMorph, SpriteIconMorph*/
isSnapObject, copy, PushButtonMorph, SpriteIconMorph, Process*/
// Global stuff ////////////////////////////////////////////////////////
modules.blocks = '2016-July-15';
modules.blocks = '2016-October-11';
var SyntaxElementMorph;
var BlockMorph;
@ -186,7 +186,7 @@ WorldMorph.prototype.customMorphs = function () {
/*
return [
new SymbolMorph(
'pipette',
'stepForward',
50,
new Color(250, 250, 250),
new Point(-1, -1),
@ -346,6 +346,7 @@ SyntaxElementMorph.prototype.init = function (silently) {
this.cachedClr = null;
this.cachedClrBright = null;
this.cachedClrDark = null;
this.cachedNormalColor = null; // for single-stepping
this.isStatic = false; // if true, I cannot be exchanged
SyntaxElementMorph.uber.init.call(this, silently);
@ -731,6 +732,20 @@ SyntaxElementMorph.prototype.setLabelColor = function (
});
};
SyntaxElementMorph.prototype.flash = function () {
if (!this.cachedNormalColor) {
this.cachedNormalColor = this.color;
this.setColor(this.activeHighlight);
}
};
SyntaxElementMorph.prototype.unflash = function () {
if (this.cachedNormalColor) {
var clr = this.cachedNormalColor;
this.cachedNormalColor = null;
this.setColor(clr);
}
};
// SyntaxElementMorph zebra coloring
@ -2527,10 +2542,10 @@ BlockMorph.prototype.deleteBlock = function () {
}
});
}
if (this instanceof ReporterBlockMorph) {
if (this.parent instanceof BlockMorph) {
this.parent.revertToDefaultInput(this);
}
if ((this.parent instanceof BlockMorph)
|| (this.parent instanceof MultiArgMorph)
|| (this.parent instanceof ReporterSlotMorph)) {
this.parent.revertToDefaultInput(this);
} else { // CommandBlockMorph
if (this.parent) {
if (this.parent.fixLayout) {
@ -5835,7 +5850,8 @@ ArgMorph.prototype.reactToSliderEdit = function () {
}
if (receiver) {
stage = receiver.parentThatIsA(StageMorph);
if (stage && stage.isThreadSafe) {
if (stage && (stage.isThreadSafe ||
Process.prototype.enableSingleStepping)) {
stage.threads.startProcess(top, stage.isThreadSafe);
} else {
top.mouseClickLeft();
@ -7639,6 +7655,29 @@ InputSlotMorph.prototype.isEmptySlot = function () {
return this.contents().text === '';
};
// InputSlotMorph single-stepping:
InputSlotMorph.prototype.flash = function () {
// don't redraw the label b/c zebra coloring
if (!this.cachedNormalColor) {
this.cachedNormalColor = this.color;
this.color = this.activeHighlight;
this.drawNew();
this.changed();
}
};
InputSlotMorph.prototype.unflash = function () {
// don't redraw the label b/c zebra coloring
if (this.cachedNormalColor) {
var clr = this.cachedNormalColor;
this.cachedNormalColor = null;
this.color = clr;
this.drawNew();
this.changed();
}
};
// InputSlotMorph drawing:
InputSlotMorph.prototype.drawNew = function () {
@ -7647,13 +7686,15 @@ InputSlotMorph.prototype.drawNew = function () {
// initialize my surface property
this.image = newCanvas(this.extent());
context = this.image.getContext('2d');
if (this.parent) {
if (this.cachedNormalColor) { // if flashing
borderColor = this.color;
} else if (this.parent) {
borderColor = this.parent.color;
} else {
borderColor = new Color(120, 120, 120);
}
context.fillStyle = this.color.toString();
if (this.isReadOnly) {
if (this.isReadOnly && !this.cachedNormalColor) { // unless flashing
context.fillStyle = borderColor.darker().toString();
}
@ -7994,6 +8035,16 @@ TemplateSlotMorph.prototype.drawNew = function () {
TemplateSlotMorph.prototype.drawRounded = ReporterBlockMorph
.prototype.drawRounded;
// TemplateSlotMorph single-stepping
TemplateSlotMorph.prototype.flash = function () {
this.template().flash();
};
TemplateSlotMorph.prototype.unflash = function () {
this.template().unflash();
};
// BooleanSlotMorph ////////////////////////////////////////////////////
/*
@ -8142,7 +8193,10 @@ BooleanSlotMorph.prototype.drawNew = function (progress) {
this.fontSize + this.edge * 2
));
}
this.color = this.parent ? this.parent.color : new Color(200, 200, 200);
if (!(this.cachedNormalColor)) { // unless flashing
this.color = this.parent ?
this.parent.color : new Color(200, 200, 200);
}
this.cachedClr = this.color.toString();
this.cachedClrBright = this.bright();
this.cachedClrDark = this.dark();
@ -8162,15 +8216,19 @@ BooleanSlotMorph.prototype.drawDiamond = function (context, progress) {
gradient;
// draw the 'flat' shape:
switch (this.value) {
case true:
context.fillStyle = 'rgb(0, 200, 0)';
break;
case false:
context.fillStyle = 'rgb(200, 0, 0)';
break;
default:
context.fillStyle = this.color.darker(25).toString();
if (this.cachedNormalColor) { // if flashing
context.fillStyle = this.color.toString();
} else {
switch (this.value) {
case true:
context.fillStyle = 'rgb(0, 200, 0)';
break;
case false:
context.fillStyle = 'rgb(200, 0, 0)';
break;
default:
context.fillStyle = this.color.darker(25).toString();
}
}
if (progress && !this.isEmptySlot()) {
@ -8680,6 +8738,7 @@ SymbolMorph.uber = Morph.prototype;
SymbolMorph.prototype.names = [
'square',
'pointRight',
'stepForward',
'gears',
'file',
'fullScreen',
@ -8802,6 +8861,8 @@ SymbolMorph.prototype.symbolCanvasColored = function (aColor) {
return this.drawSymbolStop(canvas, aColor);
case 'pointRight':
return this.drawSymbolPointRight(canvas, aColor);
case 'stepForward':
return this.drawSymbolStepForward(canvas, aColor);
case 'gears':
return this.drawSymbolGears(canvas, aColor);
case 'file':
@ -8945,6 +9006,28 @@ SymbolMorph.prototype.drawSymbolPointRight = function (canvas, color) {
return canvas;
};
SymbolMorph.prototype.drawSymbolStepForward = function (canvas, color) {
// answer a canvas showing a right-pointing triangle
// followed by a vertical bar
var ctx = canvas.getContext('2d');
ctx.fillStyle = color.toString();
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(canvas.width * 0.75, Math.round(canvas.height / 2));
ctx.lineTo(0, canvas.height);
ctx.lineTo(0, 0);
ctx.closePath();
ctx.fill();
ctx.fillRect(
canvas.width * 0.75,
0,
canvas.width * 0.25,
canvas.height
);
return canvas;
};
SymbolMorph.prototype.drawSymbolGears = function (canvas, color) {
// answer a canvas showing gears
var ctx = canvas.getContext('2d'),
@ -12188,6 +12271,12 @@ ScriptFocusMorph.prototype.insertBlock = function (block) {
}
}
}
// experimental: if the inserted block has inputs, go to the first one
if (this.element.inputs && this.element.inputs().length) {
this.atEnd = false;
this.nextElement();
}
};
ScriptFocusMorph.prototype.insertVariableGetter = function () {

38
byob.js
Wyświetl plik

@ -108,7 +108,7 @@ WatcherMorph, Variable*/
// Global stuff ////////////////////////////////////////////////////////
modules.byob = '2016-July-14';
modules.byob = '2016-September-24';
// Declarations
@ -150,6 +150,7 @@ function CustomBlockDefinition(spec, receiver) {
// don't serialize (not needed for functionality):
this.receiver = receiver || null; // for serialization only (pointer)
this.editorDimensions = null; // a rectangle, last bounds of the editor
this.cachedIsRecursive = null; // for automatic yielding
}
// CustomBlockDefinition instantiating blocks
@ -343,6 +344,26 @@ CustomBlockDefinition.prototype.parseSpec = function (spec) {
return parts;
};
CustomBlockDefinition.prototype.isDirectlyRecursive = function () {
var myspec;
if (this.cachedIsRecursive !== null) {
return this.cachedIsRecursive;
}
if (!this.body) {
this.cachedIsRecursive = false;
} else {
myspec = this.spec;
this.cachedIsRecursive = this.body.expression.anyChild(
function (morph) {
return morph instanceof BlockMorph &&
morph.definition &&
morph.definition.spec === myspec;
}
);
}
return this.cachedIsRecursive;
};
// CustomBlockDefinition picturing
CustomBlockDefinition.prototype.scriptsPicture = function () {
@ -1754,7 +1775,9 @@ function BlockEditorMorph(definition, target) {
}
BlockEditorMorph.prototype.init = function (definition, target) {
var scripts, proto, scriptsFrame, block, comment, myself = this;
var scripts, proto, scriptsFrame, block, comment, myself = this,
isLive = Process.prototype.enableLiveCoding ||
Process.prototype.enableSingleStepping;
// additional properties:
this.definition = definition;
@ -1789,7 +1812,9 @@ BlockEditorMorph.prototype.init = function (definition, target) {
comment.block = proto;
}
if (definition.body !== null) {
proto.nextBlock(definition.body.expression.fullCopy());
proto.nextBlock(isLive ? definition.body.expression
: definition.body.expression.fullCopy()
);
}
scripts.add(proto);
proto.fixBlockColor(null, true);
@ -1819,8 +1844,10 @@ BlockEditorMorph.prototype.init = function (definition, target) {
this.addBody(scriptsFrame);
this.addButton('ok', 'OK');
this.addButton('updateDefinition', 'Apply');
this.addButton('cancel', 'Cancel');
if (!isLive) {
this.addButton('updateDefinition', 'Apply');
this.addButton('cancel', 'Cancel');
}
this.setExtent(new Point(375, 300)); // normal initial extent
this.fixLayout();
@ -1962,6 +1989,7 @@ BlockEditorMorph.prototype.updateDefinition = function () {
this.definition.variableNames = this.variableNames();
this.definition.scripts = [];
this.definition.editorDimensions = this.bounds.copy();
this.definition.cachedIsRecursive = null; // flush the cache, don't update
this.body.contents.children.forEach(function (morph) {
if (morph instanceof PrototypeHatBlockMorph) {

73
gui.js
Wyświetl plik

@ -68,11 +68,11 @@ fontHeight, hex_sha512, sb, CommentMorph, CommandBlockMorph,
BlockLabelPlaceHolderMorph, Audio, SpeechBubbleMorph, ScriptFocusMorph,
XML_Element, WatcherMorph, BlockRemovalDialogMorph, saveAs, TableMorph,
isSnapObject, isRetinaEnabled, disableRetinaSupport, enableRetinaSupport,
isRetinaSupported*/
isRetinaSupported, SliderMorph*/
// Global stuff ////////////////////////////////////////////////////////
modules.gui = '2016-August-12';
modules.gui = '2016-October-10';
// Declarations
@ -536,6 +536,7 @@ IDE_Morph.prototype.createControlBar = function () {
// assumes the logo has already been created
var padding = 5,
button,
slider,
stopButton,
pauseButton,
startButton,
@ -718,6 +719,23 @@ IDE_Morph.prototype.createControlBar = function () {
this.controlBar.add(startButton);
this.controlBar.startButton = startButton;
// steppingSlider
slider = new SliderMorph(
61,
1,
Process.prototype.flashTime * 100 + 1,
6,
'horizontal'
);
slider.action = function (num) {
Process.prototype.flashTime = (num - 1) / 100;
myself.controlBar.refreshResumeSymbol();
};
slider.alpha = MorphicPreferences.isFlat ? 0.1 : 0.3;
slider.setExtent(new Point(50, 14));
this.controlBar.add(slider);
this.controlBar.steppingSlider = slider;
// projectButton
button = new PushButtonMorph(
this,
@ -814,6 +832,9 @@ IDE_Morph.prototype.createControlBar = function () {
}
);
slider.setCenter(myself.controlBar.center());
slider.setRight(stageSizeButton.left() - padding);
settingsButton.setCenter(myself.controlBar.center());
settingsButton.setLeft(this.left());
@ -823,9 +844,41 @@ IDE_Morph.prototype.createControlBar = function () {
projectButton.setCenter(myself.controlBar.center());
projectButton.setRight(cloudButton.left() - padding);
this.refreshSlider();
this.updateLabel();
};
this.controlBar.refreshSlider = function () {
if (Process.prototype.enableSingleStepping && !myself.isAppMode) {
slider.drawNew();
slider.show();
} else {
slider.hide();
}
this.refreshResumeSymbol();
};
this.controlBar.refreshResumeSymbol = function () {
var pauseSymbols;
if (Process.prototype.enableSingleStepping &&
Process.prototype.flashTime > 0.5) {
myself.stage.threads.pauseAll(myself.stage);
pauseSymbols = [
new SymbolMorph('pause', 12),
new SymbolMorph('stepForward', 14)
];
} else {
pauseSymbols = [
new SymbolMorph('pause', 12),
new SymbolMorph('pointRight', 14)
];
}
pauseButton.labelString = pauseSymbols;
pauseButton.createLabel();
pauseButton.fixLayout();
pauseButton.refresh();
};
this.controlBar.updateLabel = function () {
var suffix = myself.world().isDevMode ?
' - ' + localize('development mode') : '';
@ -967,6 +1020,7 @@ IDE_Morph.prototype.createPalette = function (forSearching) {
}
this.palette.isDraggable = false;
this.palette.acceptsDrops = true;
this.palette.enableAutoScrolling = false;
this.palette.contents.acceptsDrops = false;
this.palette.reactToDropOf = function (droppedMorph) {
@ -1767,6 +1821,11 @@ IDE_Morph.prototype.toggleVariableFrameRate = function () {
}
};
IDE_Morph.prototype.toggleSingleStepping = function () {
this.stage.threads.toggleSingleStepping();
this.controlBar.refreshSlider();
};
IDE_Morph.prototype.startFastTracking = function () {
this.stage.isFastTracked = true;
this.stage.fps = 0;
@ -2551,6 +2610,14 @@ IDE_Morph.prototype.settingsMenu = function () {
'EXPERIMENTAL! check to enable\n live custom control structures',
true
);
addPreference(
'Visible stepping',
'toggleSingleStepping',
Process.prototype.enableSingleStepping,
'uncheck to turn off\nvisible stepping',
'check to turn on\n visible stepping (slow)',
false
);
menu.addLine(); // everything below this line is stored in the project
addPreference(
'Thread safe scripts',
@ -2982,7 +3049,7 @@ IDE_Morph.prototype.aboutSnap = function () {
module, btn1, btn2, btn3, btn4, licenseBtn, translatorsBtn,
world = this.world();
aboutTxt = 'Snap! 4.0.8.7\nBuild Your Own Blocks\n\n'
aboutTxt = 'Snap! 4.0.9 - dev -\nBuild Your Own Blocks\n\n'
+ 'Copyright \u24B8 2016 Jens M\u00F6nig and '
+ 'Brian Harvey\n'
+ 'jens@moenig.org, bh@cs.berkeley.edu\n\n'

Wyświetl plik

@ -3004,3 +3004,71 @@ http://snap.berkeley.edu/run#cloud:Username=jens&ProjectName=rotation
* Morphic: replace deprecated KeyboardEvent.keyIdentifier with .key
== v4.0.8.7 ====
*** in development ***
160915
------
* new single stepping feature (like Scratch 1.4) with flashing blocks
* slider for single-stepping speed
* pausing now flashes the currently active blocks
160916
------
* enable single stepping for clone-scripts w. multiple blocks flashing per script
* enable single stepping for custom block definitions
* Objects: fixed #1410 (duplicating a sprite does not duplicate its sounds)
160918
------
* Treat single-stepping as thread safe (reverted on 160923)
* Allow user to trigger one step at a time, both in normal and single-stepping mode
160919
------
* new “stepForward” symbol
* dragging the single-step speed slider all the way to the left turns the “resume” side of the “pause” button into “stepForward”
160920
------
* atomic synching of single-stepping
160921
------
* remove shift-click-to-forward-one-frame option for the “resume” button
160922
------
* renamed “single stepping” to “visible stepping”, thanks, Brian!
* updated German translation
160923
------
* custom block execution: only yield if directly recursive and unwrapped (“speed up”)
* new feature: “wait 0” or “wait <empty>” now yields once, unless warped
* revert treating visual stepping as thread safe, because of music scheduling
160924
------
* dont update the recursion cache when updating a custom block definition
160929
------
* Objects: fixed #1437
161007
------
* Blocks: [Keyboard-Entry] if an inserted block has inputs, go to the first one
161010
------
* Morphic: configure autoscrolling
* GUI: suppress autoscrolling for the palette and the project dialog
161011
------
* Blocks: make sure to fix multi-args when deleting a custom reporter definition
161011
------
* Objects: fixed #1456 (collect message names from all scripts, including custom block definitions)

Wyświetl plik

@ -185,7 +185,7 @@ SnapTranslator.dict.de = {
'translator_e-mail':
'jens@moenig.org', // optional
'last_changed':
'2016-07-12', // this, too, will appear in the Translators tab
'2016-09-22', // this, too, will appear in the Translators tab
// GUI
// control bar:
@ -821,6 +821,8 @@ SnapTranslator.dict.de = {
'Tabellenunterstützung',
'Table lines':
'Tabellen mit Linien',
'Visible stepping':
'Programmausführung verfolgen',
'Thread safe scripts':
'Threadsicherheit',
'uncheck to allow\nscript reentrance':

Wyświetl plik

@ -42,7 +42,7 @@
/*global modules, contains*/
modules.locale = '2016-July-14';
modules.locale = '2016-September-22';
// Global stuff
@ -160,7 +160,7 @@ SnapTranslator.dict.de = {
'translator_e-mail':
'jens@moenig.org',
'last_changed':
'2016-07-12'
'2016-09-22'
};
SnapTranslator.dict.it = {

Wyświetl plik

@ -82,7 +82,7 @@ SpeechBubbleMorph, RingMorph, isNil, FileReader, TableDialogMorph,
BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, localize,
TableMorph, TableFrameMorph, normalizeCanvas, BooleanSlotMorph*/
modules.objects = '2016-July-19';
modules.objects = '2016-October-12';
var SpriteMorph;
var StageMorph;
@ -1409,9 +1409,11 @@ SpriteMorph.prototype.fullCopy = function (forClone) {
c.costumes = new List(arr);
arr = [];
this.sounds.asArray().forEach(function (sound) {
arr.push(sound);
var snd = forClone ? sound : sound.copy();
arr.push(snd);
});
c.sounds = new List(arr);
arr = [];
c.nestingScale = 1;
c.rotatesWithAnchor = true;
c.anchor = null;
@ -2950,8 +2952,10 @@ SpriteMorph.prototype.setColor = function (aColor) {
y = this.yPosition();
if (!this.color.eq(aColor)) {
this.color = aColor.copy();
this.drawNew();
this.gotoXY(x, y);
if (!this.costume) {
this.drawNew();
this.silentGotoXY(x, y);
}
}
};
@ -4000,14 +4004,33 @@ SpriteMorph.prototype.yCenter = function () {
// SpriteMorph message broadcasting
SpriteMorph.prototype.allMessageNames = function () {
var msgs = [];
this.scripts.allChildren().forEach(function (morph) {
var txt;
if (morph.selector) {
if (contains(
['receiveMessage', 'doBroadcast', 'doBroadcastAndWait'],
morph.selector
)) {
var msgs = [],
all = this.scripts.children.slice();
this.customBlocks.forEach(function (def) {
if (def.body) {
all.push(def.body.expression);
}
def.scripts.forEach(function (scr) {
all.push(scr);
});
});
if (this.globalBlocks) {
this.globalBlocks.forEach(function (def) {
if (def.body) {
all.push(def.body.expression);
}
def.scripts.forEach(function (scr) {
all.push(scr);
});
});
}
all.forEach(function (script) {
script.allChildren().forEach(function (morph) {
var txt;
if (morph.selector && contains(
['receiveMessage', 'doBroadcast', 'doBroadcastAndWait'],
morph.selector
)) {
txt = morph.inputs()[0].evaluate();
if (isString(txt) && txt !== '') {
if (!contains(msgs, txt)) {
@ -4015,7 +4038,7 @@ SpriteMorph.prototype.allMessageNames = function () {
}
}
}
}
});
});
return msgs;
};
@ -5435,7 +5458,7 @@ StageMorph.prototype.reactToDropOf = function (morph, hand) {
// StageMorph stepping
StageMorph.prototype.step = function () {
var current, elapsed, leftover, world = this.world();
var current, elapsed, leftover, ide, world = this.world();
// handle keyboard events
if (world.keyboardReceiver === null) {
@ -5471,6 +5494,14 @@ StageMorph.prototype.step = function () {
this.changed();
} else {
this.threads.step();
// single-stepping hook:
if (this.threads.wantsToPause) {
ide = this.parentThatIsA(IDE_Morph);
if (ide) {
ide.controlBar.pauseButton.refresh();
}
}
}
// update watchers

Wyświetl plik

@ -61,7 +61,7 @@ StageMorph, SpriteMorph, StagePrompterMorph, Note, modules, isString, copy,
isNil, WatcherMorph, List, ListWatcherMorph, alert, console, TableMorph,
TableFrameMorph, isSnapObject*/
modules.threads = '2016-August-12';
modules.threads = '2016-September-23';
var ThreadManager;
var Process;
@ -175,6 +175,7 @@ function invoke(
function ThreadManager() {
this.processes = [];
this.wantsToPause = false; // single stepping support
}
ThreadManager.prototype.pauseCustomHatBlocks = false;
@ -275,6 +276,25 @@ ThreadManager.prototype.step = function () {
// for sprites that are currently picked up, then filter out any
// processes that have been terminated
var isInterrupted;
if (Process.prototype.enableSingleStepping) {
this.processes.forEach(function (proc) {
if (proc.isInterrupted) {
proc.runStep();
isInterrupted = true;
} else {
proc.lastYield = Date.now();
}
});
this.wantsToPause = (Process.prototype.flashTime > 0.5);
if (isInterrupted) {
if (this.wantsToPause) {
this.pauseAll();
}
return;
}
}
this.processes.forEach(function (proc) {
if (!proc.homeContext.receiver.isPickedUp() && !proc.isDead) {
proc.runStep();
@ -290,6 +310,7 @@ ThreadManager.prototype.removeTerminatedProcesses = function () {
var result;
if ((!proc.isRunning() && !proc.errorFlag) || proc.isDead) {
if (proc.topBlock instanceof BlockMorph) {
proc.unflash();
proc.topBlock.removeHighlight();
}
if (proc.prompter) {
@ -371,6 +392,18 @@ ThreadManager.prototype.doWhen = function (block, stopIt) {
}
};
ThreadManager.prototype.toggleSingleStepping = function () {
Process.prototype.enableSingleStepping =
!Process.prototype.enableSingleStepping;
if (!Process.prototype.enableSingleStepping) {
this.processes.forEach(function (proc) {
if (!proc.isPaused) {
proc.unflash();
}
});
}
};
// Process /////////////////////////////////////////////////////////////
/*
@ -429,6 +462,8 @@ ThreadManager.prototype.doWhen = function (block, stopIt) {
procedureCount number counting procedure call entries,
used to tag custom block calls, so "stop block"
invocations can catch them
flashingContext for single stepping
isInterrupted boolean, indicates intra-step flashing of blocks
*/
Process.prototype = {};
@ -436,6 +471,8 @@ Process.prototype.constructor = Process;
Process.prototype.timeout = 500; // msecs after which to force yield
Process.prototype.isCatchingErrors = true;
Process.prototype.enableLiveCoding = false; // experimental
Process.prototype.enableSingleStepping = false; // experimental
Process.prototype.flashTime = 0; // experimental
function Process(topBlock, onComplete, rightAway) {
this.topBlock = topBlock || null;
@ -459,6 +496,8 @@ function Process(topBlock, onComplete, rightAway) {
this.exportResult = false;
this.onComplete = onComplete || null;
this.procedureCount = 0;
this.flashingContext = null; // experimental, for single-stepping
this.isInterrupted = false; // experimental, for single-stepping
if (topBlock) {
this.homeContext.receiver = topBlock.receiver();
@ -490,9 +529,10 @@ Process.prototype.runStep = function (deadline) {
if (this.isPaused) { // allow pausing in between atomic steps:
return this.pauseStep();
}
this.readyToYield = false;
while (!this.readyToYield
this.isInterrupted = false;
while (!this.readyToYield && !this.isInterrupted
&& this.context
&& (Date.now() - this.lastYield < this.timeout)
) {
@ -510,6 +550,7 @@ Process.prototype.runStep = function (deadline) {
}
this.evaluateContext();
}
this.lastYield = Date.now();
this.isFirstStep = false;
@ -544,13 +585,20 @@ Process.prototype.stop = function () {
};
Process.prototype.pause = function () {
if (this.readyToTerminate) {
return;
}
this.isPaused = true;
this.flashPausedContext();
if (this.context && this.context.startTime) {
this.pauseOffset = Date.now() - this.context.startTime;
}
};
Process.prototype.resume = function () {
if (!this.enableSingleStepping) {
this.unflash();
}
this.isPaused = false;
this.pauseOffset = null;
};
@ -586,7 +634,7 @@ Process.prototype.evaluateContext = function () {
return this.evaluateBlock(exp, exp.inputs().length);
}
if (isString(exp)) {
return this[exp]();
return this[exp].apply(this, this.context.inputs);
}
this.popContext(); // default: just ignore it
};
@ -607,6 +655,7 @@ Process.prototype.evaluateBlock = function (block, argCount) {
if (argCount > inputs.length) {
this.evaluateNextInput(block);
} else {
if (this.flashContext()) {return; } // yield to flash the block
if (this[selector]) {
rcvr = this;
}
@ -636,11 +685,13 @@ Process.prototype.reportOr = function (block) {
if (inputs.length < 1) {
this.evaluateNextInput(block);
} else if (inputs[0]) {
if (this.flashContext()) {return; }
this.returnValueToParentContext(true);
this.popContext();
} else if (inputs.length < 2) {
this.evaluateNextInput(block);
} else {
if (this.flashContext()) {return; }
this.returnValueToParentContext(inputs[1] === true);
this.popContext();
}
@ -652,11 +703,13 @@ Process.prototype.reportAnd = function (block) {
if (inputs.length < 1) {
this.evaluateNextInput(block);
} else if (!inputs[0]) {
if (this.flashContext()) {return; }
this.returnValueToParentContext(false);
this.popContext();
} else if (inputs.length < 2) {
this.evaluateNextInput(block);
} else {
if (this.flashContext()) {return; }
this.returnValueToParentContext(inputs[1] === true);
this.popContext();
}
@ -664,6 +717,7 @@ Process.prototype.reportAnd = function (block) {
Process.prototype.doReport = function (block) {
var outer = this.context.outerContext;
if (this.flashContext()) {return; } // flash the block here, special form
if (this.isClicked && (block.topBlock() === this.topBlock)) {
this.isShowingResult = true;
}
@ -736,6 +790,7 @@ Process.prototype.evaluateArgLabel = function (argLabel) {
Process.prototype.evaluateInput = function (input) {
// evaluate the input unless it is bound to an implicit parameter
var ans;
if (this.flashContext()) {return; } // yield to flash the current argMorph
if (input.bindingID) {
if (this.isCatchingErrors) {
try {
@ -879,7 +934,8 @@ Process.prototype.reify = function (topBlock, parameterNames, isCustomBlock) {
i = 0;
if (topBlock) {
context.expression = this.enableLiveCoding ?
context.expression = this.enableLiveCoding ||
this.enableSingleStepping ?
topBlock : topBlock.fullCopy();
context.expression.show(); // be sure to make visible if in app mode
@ -898,7 +954,8 @@ Process.prototype.reify = function (topBlock, parameterNames, isCustomBlock) {
}
} else {
context.expression = this.enableLiveCoding ? [this.context.expression]
context.expression = this.enableLiveCoding ||
this.enableSingleStepping ? [this.context.expression]
: [this.context.expression.fullCopy()];
}
@ -1286,8 +1343,9 @@ Process.prototype.evaluateCustomBlock = function () {
if (caller && !caller.tag) {
caller.tag = this.procedureCount;
}
// yield commands unless explicitly "warped"
if (!this.isAtomic) {
// yield commands unless explicitly "warped" or directly recursive
if (!this.isAtomic &&
this.context.expression.definition.isDirectlyRecursive()) {
this.readyToYield = true;
}
}
@ -1911,6 +1969,11 @@ Process.prototype.doWait = function (secs) {
this.context.startTime = Date.now();
}
if ((Date.now() - this.context.startTime) >= (secs * 1000)) {
if (!this.isAtomic && (secs === 0)) {
// "wait 0 secs" is a plain "yield"
// that can be overridden by "warp"
this.readyToYield = true;
}
return null;
}
this.pushContext('doYield');
@ -3276,6 +3339,67 @@ Process.prototype.reportFrameCount = function () {
return this.frameCount;
};
// Process single-stepping
Process.prototype.flashContext = function () {
var expr = this.context.expression;
if (this.enableSingleStepping &&
!this.isAtomic &&
expr instanceof SyntaxElementMorph &&
!(expr instanceof CommandSlotMorph) &&
!this.context.isFlashing &&
expr.world()) {
this.unflash();
expr.flash();
this.context.isFlashing = true;
this.flashingContext = this.context;
if (this.flashTime > 0 && (this.flashTime <= 0.5)) {
this.pushContext('doIdle');
this.context.addInput(this.flashTime);
} else {
this.pushContext('doInterrupt');
}
return true;
}
return false;
};
Process.prototype.flashPausedContext = function () {
var flashable = this.context ? this.context.lastFlashable() : null;
if (flashable) {
this.unflash();
flashable.expression.flash();
flashable.isFlashing = true;
this.flashingContext = flashable;
}
};
Process.prototype.doInterrupt = function () {
this.popContext();
if (!this.isAtomic) {
this.isInterrupted = true;
}
};
Process.prototype.doIdle = function (secs) {
if (!this.context.startTime) {
this.context.startTime = Date.now();
}
if ((Date.now() - this.context.startTime) < (secs * 1000)) {
this.pushContext('doInterrupt');
return;
}
this.popContext();
};
Process.prototype.unflash = function () {
if (this.flashingContext) {
this.flashingContext.expression.unflash();
this.flashingContext.isFlashing = false;
this.flashingContext = null;
}
};
// Context /////////////////////////////////////////////////////////////
/*
@ -3306,6 +3430,7 @@ Process.prototype.reportFrameCount = function () {
emptySlots caches the number of empty slots for reification
tag string or number to optionally identify the Context,
as a "return" target (for the "stop block" primitive)
isFlashing flag for single-stepping
*/
function Context(
@ -3331,6 +3456,7 @@ function Context(
this.isCustomBlock = false; // marks the end of a custom block's stack
this.emptySlots = 0; // used for block reification
this.tag = null; // lexical catch-tag for custom blocks
this.isFlashing = false; // for single-stepping
}
Context.prototype.toString = function () {
@ -3464,6 +3590,19 @@ Context.prototype.stopMusic = function () {
}
};
// Context single-stepping:
Context.prototype.lastFlashable = function () {
// for experimental single-stepping when pausing
if (this.expression instanceof SyntaxElementMorph &&
!(this.expression instanceof CommandSlotMorph)) {
return this;
} else if (this.parentContext) {
return this.parentContext.lastFlashable();
}
return null;
};
// Context debugging
Context.prototype.stackSize = function () {