towards v4.0.4 - under construction -

* Show result bubble when the user clicks on a command script that uses
REPORT (You can now click on REPORT and it actually does something)
* New generic “When” hat block, enhances red stop button behavior
* New block (instance) variables feature (experimental)
* evaluator performance optimizations
* Morphic grab-threshold fix for scroll frames
* fixed several block rendering glitches
* List category LENGTH reporter now also works on text
* Changed “any” to “random” (in English only)
* new FILL primitive in the Pen category
* switched to animation frame scheduling, please use TURBO for music
* Updated German translation
dev
Jens Mönig 2015-12-15 10:14:56 +01:00
rodzic b4af8f52e3
commit f24b65f673
13 zmienionych plików z 825 dodań i 105 usunięć

120
blocks.js
Wyświetl plik

@ -156,7 +156,7 @@ DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph, Costume*/
// Global stuff ////////////////////////////////////////////////////////
modules.blocks = '2015-November-17';
modules.blocks = '2015-December-15';
var SyntaxElementMorph;
var BlockMorph;
@ -485,6 +485,10 @@ SyntaxElementMorph.prototype.replaceInput = function (oldArg, newArg) {
if ((idx === -1) || (scripts === null)) {
return null;
}
if (oldArg.cachedSlotSpec) {oldArg.cachedSlotSpec = null; }
if (newArg.cachedSlotSpec) {newArg.cachedSlotSpec = null; }
this.startLayout();
if (newArg.parent) {
newArg.parent.removeChild(newArg);
@ -532,6 +536,9 @@ SyntaxElementMorph.prototype.silentReplaceInput = function (oldArg, newArg) {
return;
}
if (oldArg.cachedSlotSpec) {oldArg.cachedSlotSpec = null; }
if (newArg.cachedSlotSpec) {newArg.cachedSlotSpec = null; }
if (newArg.parent) {
newArg.parent.removeChild(newArg);
}
@ -628,6 +635,10 @@ SyntaxElementMorph.prototype.getVarNamesDict = function () {
rcvr = block.receiver();
block.allParents().forEach(function (morph) {
if (morph instanceof PrototypeHatBlockMorph) {
tempVars.push.apply(
tempVars,
morph.variableNames()
);
tempVars.push.apply(
tempVars,
morph.inputs()[0].inputFragmentNames()
@ -780,6 +791,10 @@ SyntaxElementMorph.prototype.labelPart = function (spec) {
part = new MultiArgMorph('%t', null, 1, spec);
part.canBeEmpty = false;
break;
case '%blockVars':
part = new MultiArgMorph('%t', 'block variables', 0, spec);
part.canBeEmpty = false;
break;
case '%parms':
part = new MultiArgMorph('%t', 'Input Names:', 0, spec);
part.canBeEmpty = false;
@ -2125,11 +2140,9 @@ BlockMorph.prototype.setSpec = function (spec, silently) {
if (part instanceof RingMorph) {
part.fixBlockColor();
}
if (part instanceof MultiArgMorph
|| contains(
[CommandSlotMorph, RingCommandSlotMorph],
part.constructor
)) {
if (part instanceof MultiArgMorph ||
part.constructor === CommandSlotMorph ||
part.constructor === RingCommandSlotMorph) {
part.fixLayout();
}
if (myself.isPrototype) {
@ -2374,7 +2387,7 @@ BlockMorph.prototype.developersMenu = function () {
new DialogBoxMorph(
this,
this.setSpec,
this.userSetSpec,
this
).prompt(
menu.title + '\nspec',
@ -2861,7 +2874,7 @@ BlockMorph.prototype.codeMappingHeader = function () {
BlockMorph.prototype.eraseHoles = function (context) {
var myself = this,
isReporter = this instanceof ReporterBlockMorph,
isRing = this instanceof RingMorph,
shift = this.edge * 0.5,
gradient,
rightX,
@ -2902,7 +2915,7 @@ BlockMorph.prototype.eraseHoles = function (context) {
context.clearRect(
hole.bounds.origin.x - myself.bounds.origin.x + 1,
hole.bounds.origin.y - myself.bounds.origin.y + 1,
isReporter ? w - 2 : w + 1,
isRing ? w - 2 : w + 1,
h
);
});
@ -3181,6 +3194,8 @@ BlockMorph.prototype.fullCopy = function () {
block.cachedInputs = null;
if (block instanceof InputSlotMorph) {
block.contents().clearSelection();
} else if (block.definition) {
block.initializeVariables();
}
} else if (block instanceof CursorMorph) {
block.destroy();
@ -3420,7 +3435,10 @@ BlockMorph.prototype.stackWidth = function () {
};
BlockMorph.prototype.snap = function () {
var top = this.topBlock();
var top = this.topBlock(),
receiver,
stage,
ide;
top.allComments().forEach(function (comment) {
comment.align(top);
});
@ -3431,6 +3449,21 @@ BlockMorph.prototype.snap = function () {
if (top.getHighlight()) {
top.addHighlight(top.removeHighlight());
}
// register generic hat blocks
if (this.selector === 'receiveCondition') {
receiver = top.receiver();
if (receiver) {
stage = receiver.parentThatIsA(StageMorph);
if (stage) {
stage.enableCustomHatBlocks = true;
stage.threads.pauseCustomHatBlocks = false;
ide = stage.parentThatIsA(IDE_Morph);
if (ide) {
ide.controlBar.stopButton.refresh();
}
}
}
}
};
// CommandBlockMorph ///////////////////////////////////////////////////
@ -3468,6 +3501,7 @@ CommandBlockMorph.prototype.init = function (silently) {
this.setExtent(new Point(200, 100), silently);
this.partOfCustomCommand = false;
this.exitTag = null;
// this.cachedNextBlock = null; // don't serialize
};
// CommandBlockMorph enumerating:
@ -3495,6 +3529,7 @@ CommandBlockMorph.prototype.nextBlock = function (block) {
var nb = this.nextBlock(),
affected = this.parentThatIsA(CommandSlotMorph);
this.add(block);
// this.cachedNextBlock = block;
if (nb) {
block.bottomBlock().nextBlock(nb);
}
@ -3508,6 +3543,18 @@ CommandBlockMorph.prototype.nextBlock = function (block) {
affected.fixLayout();
}
} else {
/* cachedNextBlock - has issues, disabled for now
if (!this.cachedNextBlock) {
this.cachedNextBlock = detect(
this.children,
function (child) {
return child instanceof CommandBlockMorph
&& !child.isPrototype;
}
);
}
return this.cachedNextBlock;
*/
return detect(
this.children,
function (child) {
@ -4330,6 +4377,7 @@ ReporterBlockMorph.prototype.init = function (isPredicate, silently) {
ReporterBlockMorph.uber.init.call(this, silently);
this.isPredicate = isPredicate || false;
this.setExtent(new Point(200, 80), silently);
this.cachedSlotSpec = null; // don't serialize
};
// ReporterBlockMorph drag & drop:
@ -4340,6 +4388,7 @@ ReporterBlockMorph.prototype.snap = function (hand) {
nb,
target;
this.cachedSlotSpec = null;
if (!scripts instanceof ScriptsMorph) {
return null;
}
@ -4388,6 +4437,7 @@ ReporterBlockMorph.prototype.prepareToBeGrabbed = function (handMorph) {
}
ReporterBlockMorph.uber.prepareToBeGrabbed.call(this, handMorph);
this.alpha = 0.85;
this.cachedSlotSpec = null;
};
// ReporterBlockMorph enumerating
@ -4400,11 +4450,12 @@ ReporterBlockMorph.prototype.blockSequence = function () {
// ReporterBlockMorph evaluating
ReporterBlockMorph.prototype.isUnevaluated = function () {
/*
answer whether my parent block's slot is designated to be of an
'unevaluated' kind, denoting a spedial form
*/
return contains(['%anyUE', '%boolUE', '%f'], this.getSlotSpec());
// answer whether my parent block's slot is designated to be of an
// 'unevaluated' kind, denoting a spedial form
var spec = this.getSlotSpec();
return spec === '%anyUE' ||
spec === '%boolUE' ||
spec === '%f';
};
ReporterBlockMorph.prototype.isLocked = function () {
@ -4414,6 +4465,28 @@ ReporterBlockMorph.prototype.isLocked = function () {
ReporterBlockMorph.prototype.getSlotSpec = function () {
// answer the spec of the slot I'm in, if any
// cached for performance
if (!this.cachedSlotSpec) {
this.cachedSlotSpec = this.determineSlotSpec();
/*
} else {
// debug slot spec caching
var real = this.determineSlotSpec();
if (real !== this.cachedSlotSpec) {
throw new Error(
'cached slot spec ' +
this.cachedSlotSpec +
' does not match: ' +
real
);
}
*/
}
return this.cachedSlotSpec;
};
ReporterBlockMorph.prototype.determineSlotSpec = function () {
// private - answer the spec of the slot I'm in, if any
var parts, idx;
if (this.parent instanceof BlockMorph) {
parts = this.parent.parts().filter(
@ -4440,19 +4513,25 @@ ReporterBlockMorph.prototype.getSlotSpec = function () {
// ReporterBlockMorph events
ReporterBlockMorph.prototype.mouseClickLeft = function (pos) {
var isRing;
var label;
if (this.parent instanceof BlockInputFragmentMorph) {
return this.parent.mouseClickLeft();
}
if (this.parent instanceof TemplateSlotMorph) {
isRing = this.parent.parent && this.parent.parent.parent &&
this.parent.parent.parent instanceof RingMorph;
if (this.parent.parent && this.parent.parent.parent &&
this.parent.parent.parent instanceof RingMorph) {
label = "Input name";
} else if (this.parent.parent.elementSpec === '%blockVars') {
label = "Block variable name";
} else {
label = "Script variable name";
}
new DialogBoxMorph(
this,
this.userSetSpec,
this
).prompt(
isRing ? "Input name" : "Script variable name",
label,
this.blockSpec,
this.world()
);
@ -9713,7 +9792,8 @@ MultiArgMorph.prototype.addInput = function (contents) {
// newPart.alpha = this.alpha ? 1 : (1 - this.alpha) / 2;
if (contents) {
newPart.setContents(contents);
} else if (this.elementSpec === '%scriptVars') {
} else if (this.elementSpec === '%scriptVars' ||
this.elementSpec === '%blockVars') {
name = '';
i = idx;
while (i > 0) {

166
byob.js
Wyświetl plik

@ -104,11 +104,11 @@ ArrowMorph, PushButtonMorph, contains, InputSlotMorph, ShadowMorph,
ToggleButtonMorph, IDE_Morph, MenuMorph, copy, ToggleElementMorph,
Morph, fontHeight, StageMorph, SyntaxElementMorph, SnapSerializer,
CommentMorph, localize, CSlotMorph, SpeechBubbleMorph, MorphicPreferences,
SymbolMorph, isNil, CursorMorph*/
SymbolMorph, isNil, CursorMorph, VariableFrame*/
// Global stuff ////////////////////////////////////////////////////////
modules.byob = '2015-November-16';
modules.byob = '2015-December-15';
// Declarations
@ -142,6 +142,7 @@ function CustomBlockDefinition(spec, receiver) {
this.spec = spec || '';
// format: {'inputName' : [type, default, options, readonly]}
this.declarations = {};
this.variableNames = [];
this.comment = null;
this.codeMapping = null; // experimental, generate text code
this.codeHeader = null; // experimental, generate text code
@ -384,7 +385,7 @@ CustomBlockDefinition.prototype.scriptsModel = function () {
proto.allComments().forEach(function (comment) {
comment.align(proto);
});
proto.children[0].fixLayout();
proto.parts()[0].fixLayout();
scripts.fixMultiArgs();
return scripts;
};
@ -409,11 +410,21 @@ CustomCommandBlockMorph.prototype.init = function (definition, isProto) {
CustomCommandBlockMorph.uber.init.call(this, true); // silently
this.category = definition.category;
this.selector = 'evaluateCustomBlock';
this.variables = null;
this.initializeVariables();
if (definition) { // needed for de-serializing
this.refresh();
}
};
CustomCommandBlockMorph.prototype.initializeVariables = function () {
var myself = this;
this.variables = new VariableFrame();
this.definition.variableNames.forEach(function (name) {
myself.variables.addVar(name);
});
};
CustomCommandBlockMorph.prototype.refresh = function (silently) {
var def = this.definition,
newSpec = this.isPrototype ?
@ -447,6 +458,10 @@ CustomCommandBlockMorph.prototype.refresh = function (silently) {
inp.setContents(def.inputNames()[idx]);
}
});
// initialize block vars
// to do: preserve values of unchanged variable names
this.initializeVariables();
};
CustomCommandBlockMorph.prototype.restoreInputs = function (oldInputs) {
@ -759,7 +774,47 @@ CustomCommandBlockMorph.prototype.isInUse = function () {
// CustomCommandBlockMorph menu:
CustomCommandBlockMorph.prototype.userMenu = function () {
var menu;
var hat = this.parentThatIsA(PrototypeHatBlockMorph),
rcvr = this.receiver(),
myself = this,
menu;
function monitor(vName) {
var stage = rcvr.parentThatIsA(StageMorph),
varFrame = myself.variables;
menu.addItem(
vName + '...',
function () {
var watcher = detect(
stage.children,
function (morph) {
return morph instanceof WatcherMorph
&& morph.target === varFrame
&& morph.getter === vName;
}
),
others;
if (watcher !== null) {
watcher.show();
watcher.fixLayout(); // re-hide hidden parts
return;
}
watcher = new WatcherMorph(
vName + ' ' + localize('(temporary)'),
SpriteMorph.prototype.blockColor.variables,
varFrame,
vName
);
watcher.setPosition(stage.position().add(10));
others = stage.watchers(watcher.left());
if (others.length > 0) {
watcher.setTop(others[others.length - 1].bottom());
}
stage.add(watcher);
watcher.fixLayout();
}
);
}
if (this.isPrototype) {
menu = new MenuMorph(this);
@ -770,6 +825,23 @@ CustomCommandBlockMorph.prototype.userMenu = function () {
},
'open a new window\nwith a picture of this script'
);
if (hat.inputs().length < 2) {
menu.addItem(
"block variables...",
function () {
hat.enableBlockVars();
},
'experimental -\nunder construction'
);
} else {
menu.addItem(
"remove block variables...",
function () {
hat.enableBlockVars(false);
},
'experimental -\nunder construction'
);
}
} else {
menu = this.constructor.uber.userMenu.call(this);
if (!menu) {
@ -780,6 +852,10 @@ CustomCommandBlockMorph.prototype.userMenu = function () {
// menu.addItem("export definition...", 'exportBlockDefinition');
menu.addItem("delete block definition...", 'deleteBlockDefinition');
this.variables.names().forEach(function (vName) {
monitor(vName);
});
}
menu.addItem("edit...", 'edit'); // works also for prototypes
return menu;
@ -938,16 +1014,19 @@ CustomReporterBlockMorph.prototype.init = function (
) {
this.definition = definition; // mandatory
this.isPrototype = isProto || false; // optional
CustomReporterBlockMorph.uber.init.call(this, isPredicate, true); // sil.
this.category = definition.category;
this.variables = new VariableFrame();
this.initializeVariables();
this.selector = 'evaluateCustomBlock';
if (definition) { // needed for de-serializing
this.refresh();
}
};
CustomReporterBlockMorph.prototype.initializeVariables =
CustomCommandBlockMorph.prototype.initializeVariables;
CustomReporterBlockMorph.prototype.refresh = function () {
CustomCommandBlockMorph.prototype.refresh.call(this, true);
if (!this.isPrototype) {
@ -1732,7 +1811,7 @@ BlockEditorMorph.prototype.init = function (definition, target) {
this.setExtent(new Point(375, 300)); // normal initial extent
this.fixLayout();
proto.children[0].fixLayout();
proto.parts()[0].fixLayout();
scripts.fixMultiArgs();
};
@ -1864,6 +1943,7 @@ BlockEditorMorph.prototype.updateDefinition = function () {
this.definition.receiver = this.target; // only for serialization
this.definition.spec = this.prototypeSpec();
this.definition.declarations = this.prototypeSlots();
this.definition.variableNames = this.variableNames();
this.definition.scripts = [];
this.definition.editorDimensions = this.bounds.copy();
@ -1940,6 +2020,14 @@ BlockEditorMorph.prototype.prototypeSlots = function () {
).parts()[0].declarationsFromFragments();
};
BlockEditorMorph.prototype.variableNames = function () {
// answer the variable declarations from my prototype hat
return detect(
this.body.contents.children,
function (c) {return c instanceof PrototypeHatBlockMorph; }
).variableNames();
};
// BlockEditorMorph layout
BlockEditorMorph.prototype.setInitialDimensions = function () {
@ -2006,7 +2094,8 @@ function PrototypeHatBlockMorph(definition) {
}
PrototypeHatBlockMorph.prototype.init = function (definition) {
var proto = definition.prototypeInstance();
var proto = definition.prototypeInstance(),
vars;
this.definition = definition;
@ -2019,6 +2108,14 @@ PrototypeHatBlockMorph.prototype.init = function (definition) {
this.color = SpriteMorph.prototype.blockColor.control;
this.category = 'control';
this.add(proto);
if (definition.variableNames.length) {
vars = this.labelPart('%blockVars');
this.add(this.labelPart('%br'));
this.add(vars);
definition.variableNames.forEach(function (name) {
vars.addInput(name);
});
}
proto.refreshPrototypeSlotTypes(); // show slot type indicators
this.fixLayout();
};
@ -2032,11 +2129,11 @@ PrototypeHatBlockMorph.prototype.mouseClickLeft = function () {
if (this.world().currentKey === 16) { // shift-clicked
return this.focus();
}
this.children[0].mouseClickLeft();
this.parts()[0].mouseClickLeft();
};
PrototypeHatBlockMorph.prototype.userMenu = function () {
return this.children[0].userMenu();
return this.parts()[0].userMenu();
};
// PrototypeHatBlockMorph zebra coloring
@ -2045,7 +2142,7 @@ PrototypeHatBlockMorph.prototype.fixBlockColor = function (
nearestBlock,
isForced
) {
var nearest = this.children[0] || nearestBlock;
var nearest = this.parts()[0] || nearestBlock;
if (!this.zebraContrast && !isForced) {
return;
@ -2068,6 +2165,25 @@ PrototypeHatBlockMorph.prototype.fixBlockColor = function (
}
};
// PrototypeHatBlockMorph block instance variables
PrototypeHatBlockMorph.prototype.variableNames = function (choice) {
var parts = this.parts();
if (parts.length < 3) {return []; }
return parts[2].evaluate();
};
PrototypeHatBlockMorph.prototype.enableBlockVars = function (choice) {
var prot = this.parts()[0];
if (choice === false) {
this.setSpec('%s', true);
} else {
this.setSpec('%s %br %blockVars', true);
}
this.replaceInput(this.parts()[0], prot);
this.spec = null;
};
// BlockLabelFragment //////////////////////////////////////////////////
// BlockLabelFragment instance creation:
@ -2306,8 +2422,11 @@ BlockLabelFragmentMorph.prototype.userMenu = function () {
tuple[0] = '$' + string;
myself.text = tuple.join('-');
myself.fragment.labelString = myself.text;
myself.parent.parent.changed();
myself.drawNew();
myself.changed();
myself.parent.parent.fixLayout();
myself.parent.parent.changed();
},
null,
this,
@ -2428,21 +2547,38 @@ BlockLabelPlaceHolderMorph.prototype.drawNew = function () {
if (this.parent.fixLayout) {
this.parent.fixLayout();
}
if (this.parent.parent instanceof PrototypeHatBlockMorph) {
this.parent.parent.fixLayout();
}
}
};
// BlockLabelPlaceHolderMorph events:
BlockLabelPlaceHolderMorph.prototype.mouseEnter = function () {
var hat = this.parentThatIsA(PrototypeHatBlockMorph);
this.isHighlighted = true;
this.drawNew();
this.changed();
if (this.plainLabel && hat) {
hat.changed();
this.drawNew();
hat.changed();
} else {
this.drawNew();
this.changed();
}
};
BlockLabelPlaceHolderMorph.prototype.mouseLeave = function () {
var hat = this.parentThatIsA(PrototypeHatBlockMorph);
this.isHighlighted = false;
this.drawNew();
this.changed();
if (this.plainLabel && hat) {
hat.changed();
this.drawNew();
hat.changed();
} else {
this.drawNew();
this.changed();
}
};
BlockLabelPlaceHolderMorph.prototype.mouseClickLeft

Wyświetl plik

@ -30,7 +30,7 @@
/*global modules, IDE_Morph, SnapSerializer, hex_sha512, alert, nop,
localize*/
modules.cloud = '2015-December-04';
modules.cloud = '2015-December-15';
// Global stuff
@ -326,7 +326,8 @@ Cloud.prototype.reconnect = function (
Cloud.prototype.saveProject = function (ide, callBack, errorCall) {
var myself = this,
pdata,
media;
media,
size;
ide.serializer.isCollectingMedia = true;
pdata = ide.serializer.serialize(ide.stage);
@ -335,6 +336,19 @@ Cloud.prototype.saveProject = function (ide, callBack, errorCall) {
ide.serializer.isCollectingMedia = false;
ide.serializer.flushMedia();
size = pdata.length + (media ? media.length : 0);
/*
if (size > 5242880) {
new DialogBoxMorph().inform(
'Snap!Cloud',
'Project exceeds 5 MB size limit',
ide.world(),
ide.cloudIcon(null, new Color(180, 0, 0))
);
throw new Error('Project exceeds 5 MB size limit');
}
*/
// check if serialized data can be parsed back again
try {
ide.serializer.parse(pdata);
@ -353,6 +367,7 @@ Cloud.prototype.saveProject = function (ide, callBack, errorCall) {
ide.serializer.isCollectingMedia = false;
ide.serializer.flushMedia();
ide.showMessage('Uploading ' + Math.round(size / 1024) + ' KB...');
myself.reconnect(
function () {
myself.callService(
@ -545,7 +560,7 @@ Cloud.prototype.callService = function (
} else {
responseList = myself.parseResponse(
request.responseText
)
);
}
callBack.call(null, responseList, service.url);
}

90
gui.js
Wyświetl plik

@ -71,7 +71,7 @@ BlockRemovalDialogMorph*/
// Global stuff ////////////////////////////////////////////////////////
modules.gui = '2015-December-04';
modules.gui = '2015-December-15';
// Declarations
@ -624,11 +624,22 @@ IDE_Morph.prototype.createControlBar = function () {
this.controlBar.appModeButton = appModeButton; // for refreshing
// stopButton
button = new PushButtonMorph(
this,
button = new ToggleButtonMorph(
null, // colors
this, // the IDE is the target
'stopAllScripts',
new SymbolMorph('octagon', 14)
[
new SymbolMorph('octagon', 14),
new SymbolMorph('square', 14)
],
function () { // query
return myself.stage ?
myself.stage.enableCustomHatBlocks &&
myself.stage.threads.pauseCustomHatBlocks
: true;
}
);
button.corner = 12;
button.color = colors[0];
button.highlightColor = colors[1];
@ -642,13 +653,15 @@ IDE_Morph.prototype.createControlBar = function () {
button.drawNew();
// button.hint = 'stop\nevery-\nthing';
button.fixLayout();
button.refresh();
stopButton = button;
this.controlBar.add(stopButton);
this.controlBar.stopButton = stopButton; // for refreshing
//pauseButton
button = new ToggleButtonMorph(
null, //colors,
myself, // the IDE is the target
this, // the IDE is the target
'togglePauseResume',
[
new SymbolMorph('pause', 12),
@ -1706,6 +1719,8 @@ IDE_Morph.prototype.pressStart = function () {
if (this.world().currentKey === 16) { // shiftClicked
this.toggleFastTracking();
} else {
this.stage.threads.pauseCustomHatBlocks = false;
this.controlBar.stopButton.refresh();
this.runScripts();
}
};
@ -1763,6 +1778,13 @@ IDE_Morph.prototype.isPaused = function () {
};
IDE_Morph.prototype.stopAllScripts = function () {
if (this.stage.enableCustomHatBlocks) {
this.stage.threads.pauseCustomHatBlocks =
!this.stage.threads.pauseCustomHatBlocks;
} else {
this.stage.threads.pauseCustomHatBlocks = false;
}
this.controlBar.stopButton.refresh();
this.stage.fireStopAllEvent();
};
@ -2719,7 +2741,7 @@ IDE_Morph.prototype.aboutSnap = function () {
module, btn1, btn2, btn3, btn4, licenseBtn, translatorsBtn,
world = this.world();
aboutTxt = 'Snap! 4.0.3\nBuild Your Own Blocks\n\n'
aboutTxt = 'Snap! 4.0.4\nBuild Your Own Blocks\n\n'
+ 'Copyright \u24B8 2015 Jens M\u00F6nig and '
+ 'Brian Harvey\n'
+ 'jens@moenig.org, bh@cs.berkeley.edu\n\n'
@ -3022,6 +3044,51 @@ IDE_Morph.prototype.saveProjectToDisk = function () {
}
};
/* alternative download mechanism, commented out in favor of blob-uris
IDE_Morph.prototype.saveProjectToDisk = function () {
var data;
if (Process.prototype.isCatchingErrors) {
try {
data = this.serializer.serialize(this.stage);
} catch (err) {
this.showMessage('Serialization failed: ' + err);
}
} else {
data = this.serializer.serialize(this.stage);
}
this.download(data, this.projectName);
};
IDE_Morph.prototype.download = function (data, fileName, mime, suffix) {
var link = document.createElement('a'),
myself = this;
mime = mime || 'data:text/xml';
fileName = fileName || 'download';
suffix = suffix || 'xml';
function saveToDisk() {
var msg = myself.showMessage('Downloading to disk...');
link.setAttribute('href', mime + ',' + data);
link.setAttribute('download', fileName + '.' + suffix);
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
msg.destroy();
}
if (Process.prototype.isCatchingErrors) {
try {
saveToDisk();
} catch (err) {
this.showMessage('Saving failed: ' + err);
}
} else {
saveToDisk();
}
};
*/
IDE_Morph.prototype.exportProject = function (name, plain) {
var menu, str;
if (name) {
@ -3494,10 +3561,11 @@ IDE_Morph.prototype.rawOpenProjectString = function (str) {
IDE_Morph.prototype.openCloudDataString = function (str) {
var msg,
myself = this;
myself = this,
size = Math.round(str.length / 1024);
this.nextSteps([
function () {
msg = myself.showMessage('Opening project...');
msg = myself.showMessage('Opening project\n' + size + ' KB...');
},
function () {nop(); }, // yield (bug in Chrome)
function () {
@ -5275,6 +5343,12 @@ ProjectDialogMorph.prototype.rawOpenCloudProject = function (proj) {
'getRawProject',
function (response) {
SnapCloud.disconnect();
/*
if (myself.world().currentKey === 16) {
myself.ide.download(response);
return;
}
*/
myself.ide.source = 'cloud';
myself.ide.droppedText(response);
if (proj.Public === 'true') {

Wyświetl plik

@ -2695,3 +2695,105 @@ end - bulk of 151116
* Cloud: doubled the number of supported backend slices
* Cloud, GUI: support new “raw” cloud project services
++++++++++++++++++++++++++
new stuff - bulk of 151215
++++++++++++++++++++++++++
151121
------
* Threads: Show result bubble when the user clicks on a command script that uses REPORT (You can now click on REPORT and it actually does something)
151124
------
* Blocks: fix a re-rendering glitch when changing block specs in dev mode
* Threads: add optional receiver (environment) to invoke() function
151125
------
* Threads, Objects, GUI, Store: Generic “When” hat block
* BYOB: fixed a rendering bug when using plain prototype labels
151126
------
* Threads, Blocks: Performance optimizations (replace “contains” with chained tests)
* German translation update (for custom hat blocks)
151127
------
* Blocks, BYOB, Store: new experimental block variables feature
* BYOB: more prototype label rendering fixes
151128
------
* BYOB, Store: Fix some bugs related to block vars (zebra coloring etc.)
151201
------
* BYOB, Blocks: Fix BlockMorph.fullCopy() for block vars
151202
------
* Threads: Only support block vars for blocks that actually define any, to avoid race conditions among parallel global blocks with the same definition that also access sprite-local variables
151207
------
* Threads, GUI: Stop button stops / restarts custom hat blocks, green flag starts custom hat blocks
151208
------
* Objects, Blocks, Threads, GUI, Store, Locale: Automatically enable/disable custom hat blocks when theyre used in a project
* BYOB: initialize custom block vars on every definition-refresh
151209
------
* Threads: allow invoke() to operate on both blocks and rings with arguments
* Blocks: cache reporter slot specs for evaluation performance (30% speedup)
151210
------
* Store: persist block (instance) vars
* Threads: only show result bubble on user-clicked scripts if “Report” is in the lexical script (not inside a reporter block definition)
* Morphic: obey grab threshold when dragging inside scroll frames
151211
------
* Threads: extend red LENGTH reporter to also work on Text
* GUI, Objects, Blocks: extend the red stop button to reflect whether custom hat blocks are paused (indicated by a red square instead of the stop sign)
* Blocks: Tweak C-Slots to better fit inside reporters
151212
------
* Locale: change English any (in “item of”) to random because teachers
151214
------
* Objects: added “fill” primitive to the Pen category
* Updated German translation
* GUI: Directly download projects from cloud by holding shift while opening - commented out
* GUI, Cloud: show size of uploaded / downloaded projects
* GUI, Cloud: upload size limit of 5 MB - commented out
151215
------
* snap.html: switch to animation frame scheduling because Chrome sucks sooooo much!!!!
* GUI: pushed version to 4.0.4
++++++++++++++++++++++++++
v4.0.4 draft features:
* Show result bubble when the user clicks on a command script that uses REPORT (You can now click on REPORT and it actually does something)
* New generic “When” hat block, enhances red stop button behavior
* New block (instance) variables feature (experimental)
* evaluator performance optimizations
* Morphic grab-threshold fix for scroll frames
* fixed several block rendering glitches
* List category LENGTH reporter now also works on text
* Changed “any” to “random” (in English only)
* new FILL primitive in the Pen category
* switched to animation frame scheduling, please use TURBO for music
* Updated German translation
++++++++++++++++++++++++++
end - bulk of 151215
++++++++++++++++++++++++++

Wyświetl plik

@ -185,7 +185,7 @@ SnapTranslator.dict.de = {
'translator_e-mail':
'jens@moenig.org', // optional
'last_changed':
'2015-10-07', // this, too, will appear in the Translators tab
'2015-12-14', // this, too, will appear in the Translators tab
// GUI
// control bar:
@ -411,6 +411,8 @@ SnapTranslator.dict.de = {
'setze Stiftdicke auf %n',
'stamp':
'stemple',
'fill':
'male aus',
// control:
'when %greenflag clicked':
@ -429,6 +431,8 @@ SnapTranslator.dict.de = {
'vom Mauszeiger betreten',
'mouse-departed':
'vom Mauszeiger verlassen',
'when %b':
'Wenn %b',
'when I receive %msgHat':
'Wenn ich %msgHat empfange',
'broadcast %msg':

Wyświetl plik

@ -42,7 +42,7 @@
/*global modules, contains*/
modules.locale = '2015-November-16';
modules.locale = '2015-December-15';
// Global stuff
@ -123,7 +123,11 @@ SnapTranslator.dict.en = {
'translator_e-mail':
'jens@moenig.org',
'last_changed':
'2012-10-16',
'2015-12-12',
// rewordings in English avoiding having to adjust all other translations
'any':
'random',
// long strings look-up only
'file menu import hint':
@ -149,7 +153,7 @@ SnapTranslator.dict.de = {
'translator_e-mail':
'jens@moenig.org',
'last_changed':
'2015-10-07'
'2015-12-14'
};
SnapTranslator.dict.it = {

Wyświetl plik

@ -1052,7 +1052,7 @@
/*global window, HTMLCanvasElement, getMinimumFontHeight, FileReader, Audio,
FileList, getBlurredShadowSupport*/
var morphicVersion = '2015-November-16';
var morphicVersion = '2015-December-15';
var modules = {}; // keep track of additional loaded modules
var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug
@ -8896,6 +8896,7 @@ ScrollFrameMorph.prototype.mouseDownLeft = function (pos) {
return null;
}
var world = this.root(),
hand = world.hand,
oldPos = pos,
myself = this,
deltaX = 0,
@ -8904,10 +8905,18 @@ ScrollFrameMorph.prototype.mouseDownLeft = function (pos) {
this.step = function () {
var newPos;
if (world.hand.mouseButton &&
(world.hand.children.length === 0) &&
(myself.bounds.containsPoint(world.hand.position()))) {
newPos = world.hand.bounds.origin;
if (hand.mouseButton &&
(hand.children.length === 0) &&
(myself.bounds.containsPoint(hand.bounds.origin))) {
if (hand.grabPosition &&
(hand.grabPosition.distanceTo(hand.position()) <=
MorphicPreferences.grabThreshold)) {
// still within the grab threshold
return null;
}
newPos = hand.bounds.origin;
deltaX = newPos.x - oldPos.x;
if (deltaX !== 0) {
myself.scrollX(deltaX);

Wyświetl plik

@ -125,7 +125,7 @@ PrototypeHatBlockMorph*/
// Global stuff ////////////////////////////////////////////////////////
modules.objects = '2015-November-16';
modules.objects = '2015-December-15';
var SpriteMorph;
var StageMorph;
@ -569,6 +569,12 @@ SpriteMorph.prototype.initBlocks = function () {
category: 'pen',
spec: 'stamp'
},
floodFill: {
only: SpriteMorph,
type: 'command',
category: 'pen',
spec: 'fill'
},
// Control
receiveGo: {
@ -602,6 +608,11 @@ SpriteMorph.prototype.initBlocks = function () {
category: 'control',
spec: 'when I receive %msgHat'
},
receiveCondition: {
type: 'hat',
category: 'control',
spec: 'when %b'
},
doBroadcast: {
type: 'command',
category: 'control',
@ -1861,12 +1872,14 @@ SpriteMorph.prototype.blockTemplates = function (category) {
blocks.push(block('setSize'));
blocks.push('-');
blocks.push(block('doStamp'));
blocks.push(block('floodFill'));
} else if (cat === 'control') {
blocks.push(block('receiveGo'));
blocks.push(block('receiveKey'));
blocks.push(block('receiveInteraction'));
blocks.push(block('receiveCondition'));
blocks.push(block('receiveMessage'));
blocks.push('-');
blocks.push(block('doBroadcast'));
@ -3350,6 +3363,60 @@ SpriteMorph.prototype.drawLine = function (start, dest) {
}
};
SpriteMorph.prototype.floodFill = function () {
var layer = this.parent.penTrails(),
width = layer.width,
height = layer.height,
ctx = layer.getContext('2d'),
img = ctx.getImageData(0, 0, width, height),
dta = img.data,
stack = [
((height / 2) - Math.round(this.yPosition())) * width +
Math.round(this.xPosition() + (width / 2))
],
current,
src;
function read(p) {
var d = p * 4;
return [dta[d], dta[d + 1], dta[d + 2], dta[d + 3]];
}
function check(p) {
return p[0] === src[0] &&
p[1] === src[1] &&
p[2] === src[2] &&
p[3] === src[3];
}
src = read(stack[0]);
if (src[0] === Math.round(this.color.r) &&
src[1] === Math.round(this.color.g) &&
src[2] === Math.round(this.color.b) &&
src[3] === Math.round(this.color.a * 255)) {
return;
}
while (stack.length > 0) {
current = stack.pop();
if (check(read(current))) {
if (current % width > 1) {
stack.push(current + 1);
stack.push(current - 1);
}
if (current > 0 && current < height * width) {
stack.push(current + width);
stack.push(current - width);
}
}
dta[current * 4] = Math.round(this.color.r);
dta[current * 4 + 1] = Math.round(this.color.g);
dta[current * 4 + 2] = Math.round(this.color.b);
dta[current * 4 + 3] = Math.round(this.color.a * 255);
}
ctx.putImageData(img, 0, 0);
this.parent.changed();
};
// SpriteMorph motion - adjustments due to nesting
SpriteMorph.prototype.moveBy = function (delta, justMe) {
@ -3693,6 +3760,15 @@ SpriteMorph.prototype.allHatBlocksForInteraction = function (interaction) {
});
};
SpriteMorph.prototype.allGenericHatBlocks = function () {
return this.scripts.children.filter(function (morph) {
if (morph.selector) {
return morph.selector === 'receiveCondition';
}
return false;
});
};
// SpriteMorph events
SpriteMorph.prototype.mouseClickLeft = function () {
@ -4674,7 +4750,6 @@ StageMorph.prototype.hiddenPrimitives = {};
StageMorph.prototype.codeMappings = {};
StageMorph.prototype.codeHeaders = {};
StageMorph.prototype.enableCodeMapping = false;
StageMorph.prototype.enableInheritance = false;
// StageMorph instance creation
@ -4695,6 +4770,7 @@ StageMorph.prototype.init = function (globals) {
this.sounds = new List();
this.version = Date.now(); // for observers
this.isFastTracked = false;
this.enableCustomHatBlocks = true;
this.cloneCount = 0;
this.timerStart = Date.now();
@ -5036,6 +5112,9 @@ StageMorph.prototype.step = function () {
}
// manage threads
if (this.enableCustomHatBlocks) {
this.stepGenericConditions();
}
if (this.isFastTracked && this.threads.processes.length) {
this.children.forEach(function (morph) {
if (morph instanceof SpriteMorph) {
@ -5072,6 +5151,28 @@ StageMorph.prototype.step = function () {
}
};
StageMorph.prototype.stepGenericConditions = function (stopAll) {
var hats = [],
myself = this,
ide;
this.children.concat(this).forEach(function (morph) {
if (morph instanceof SpriteMorph || morph instanceof StageMorph) {
hats = hats.concat(morph.allGenericHatBlocks());
}
});
if (!hats.length) {
this.enableCustomHatBlocks = false;
ide = this.parentThatIsA(IDE_Morph);
if (ide) {
ide.controlBar.stopButton.refresh();
}
return;
}
hats.forEach(function (block) {
myself.threads.doWhen(block, stopAll);
});
};
StageMorph.prototype.developersMenu = function () {
var myself = this,
menu = StageMorph.uber.developersMenu.call(this);
@ -5430,6 +5531,7 @@ StageMorph.prototype.blockTemplates = function (category) {
blocks.push(block('receiveGo'));
blocks.push(block('receiveKey'));
blocks.push(block('receiveInteraction'));
blocks.push(block('receiveCondition'));
blocks.push(block('receiveMessage'));
blocks.push('-');
blocks.push(block('doBroadcast'));
@ -5984,6 +6086,9 @@ StageMorph.prototype.allHatBlocksForKey
StageMorph.prototype.allHatBlocksForInteraction
= SpriteMorph.prototype.allHatBlocksForInteraction;
StageMorph.prototype.allGenericHatBlocks
= SpriteMorph.prototype.allGenericHatBlocks;
// StageMorph events
StageMorph.prototype.mouseClickLeft

Wyświetl plik

@ -24,9 +24,10 @@
world = new WorldMorph(document.getElementById('world'));
world.worldCanvas.focus();
new IDE_Morph().openIn(world);
setInterval(loop, 1);
loop();
};
function loop() {
requestAnimationFrame(loop);
world.doOneCycle();
}
</script>

37
snap_fast.html 100755
Wyświetl plik

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Snap! Build Your Own Blocks</title>
<link rel="shortcut icon" href="favicon.ico">
<script type="text/javascript" src="morphic.js"></script>
<script type="text/javascript" src="widgets.js"></script>
<script type="text/javascript" src="blocks.js"></script>
<script type="text/javascript" src="threads.js"></script>
<script type="text/javascript" src="objects.js"></script>
<script type="text/javascript" src="gui.js"></script>
<script type="text/javascript" src="paint.js"></script>
<script type="text/javascript" src="lists.js"></script>
<script type="text/javascript" src="byob.js"></script>
<script type="text/javascript" src="xml.js"></script>
<script type="text/javascript" src="store.js"></script>
<script type="text/javascript" src="locale.js"></script>
<script type="text/javascript" src="cloud.js"></script>
<script type="text/javascript" src="sha512.js"></script>
<script type="text/javascript">
var world;
window.onload = function () {
world = new WorldMorph(document.getElementById('world'));
world.worldCanvas.focus();
new IDE_Morph().openIn(world);
setInterval(loop, 1);
};
function loop() {
world.doOneCycle();
}
</script>
</head>
<body style="margin: 0;">
<canvas id="world" tabindex="1" style="position: absolute;" />
</body>
</html>

Wyświetl plik

@ -61,7 +61,7 @@ SyntaxElementMorph, Variable*/
// Global stuff ////////////////////////////////////////////////////////
modules.store = '2015-October-07';
modules.store = '2015-December-15';
// XML_Serializer ///////////////////////////////////////////////////////
@ -792,7 +792,7 @@ SnapSerializer.prototype.loadCustomBlocks = function (
// private
var myself = this;
element.children.forEach(function (child) {
var definition, names, inputs, header, code, comment, i;
var definition, names, inputs, vars, header, code, comment, i;
if (child.tag !== 'block-definition') {
return;
}
@ -836,6 +836,13 @@ SnapSerializer.prototype.loadCustomBlocks = function (
});
}
vars = child.childNamed('variables');
if (vars) {
definition.variableNames = myself.loadValue(
vars.require('list')
).asArray();
}
header = child.childNamed('header');
if (header) {
definition.codeHeader = header.contents;
@ -1047,7 +1054,9 @@ SnapSerializer.prototype.loadBlock = function (model, isReporter) {
block.isDraggable = true;
inputs = block.inputs();
model.children.forEach(function (child, i) {
if (child.tag === 'comment') {
if (child.tag === 'variables') {
this.loadVariables(block.variables, child);
} else if (child.tag === 'comment') {
block.comment = this.loadComment(child);
block.comment.block = block;
} else if (child.tag === 'receiver') {
@ -1728,11 +1737,16 @@ CustomCommandBlockMorph.prototype.toBlockXML = function (serializer) {
var scope = this.definition.isGlobal ? undefined
: this.definition.receiver.name;
return serializer.format(
'<custom-block s="@"%>%%%</custom-block>',
'<custom-block s="@"%>%%%%</custom-block>',
this.blockSpec,
this.definition.isGlobal ?
'' : serializer.format(' scope="@"', scope),
serializer.store(this.inputs()),
this.definition.variableNames.length ?
'<variables>' +
this.variables.toXML(serializer) +
'</variables>'
: '',
this.comment ? this.comment.toXML(serializer) : '',
scope && !this.definition.receiver[serializer.idProperty] ?
'<receiver>' +
@ -1748,6 +1762,7 @@ CustomReporterBlockMorph.prototype.toBlockXML
CustomBlockDefinition.prototype.toXML = function (serializer) {
var myself = this;
function encodeScripts(array) {
return array.reduce(function (xml, element) {
if (element instanceof BlockMorph) {
@ -1763,6 +1778,7 @@ CustomBlockDefinition.prototype.toXML = function (serializer) {
return serializer.format(
'<block-definition s="@" type="@" category="@">' +
'%' +
(this.variableNames.length ? '<variables>%</variables>' : '@') +
'<header>@</header>' +
'<code>@</code>' +
'<inputs>%</inputs>%%' +
@ -1771,6 +1787,8 @@ CustomBlockDefinition.prototype.toXML = function (serializer) {
this.type,
this.category || 'other',
this.comment ? this.comment.toXML(serializer) : '',
(this.variableNames.length ?
serializer.store(new List(this.variableNames)) : ''),
this.codeHeader || '',
this.codeMapping || '',
Object.keys(this.declarations).reduce(function (xml, decl) {

Wyświetl plik

@ -83,7 +83,7 @@ ArgLabelMorph, localize, XML_Element, hex_sha512*/
// Global stuff ////////////////////////////////////////////////////////
modules.threads = '2015-November-16';
modules.threads = '2015-December-15';
var ThreadManager;
var Process;
@ -128,24 +128,67 @@ function snapEquals(a, b) {
return x === y;
}
function invoke(block, timeout) {
// exectue the given block synchronously, i.e. without yielding.
// if a timeout (in milliseconds) is specified, abort execution
function invoke(
action, // a BlockMorph or a Context, a reified ("ringified") block
contextArgs, // optional List of arguments for the context, or null
receiver, // optional sprite or environment
timeout, // msecs
timeoutErrorMsg, // string
suppressErrors // bool
) {
// execute the given block or context synchronously without yielding.
// Apply context (not a block) to a list of optional arguments.
// Receiver (sprite, stage or environment), timeout etc. are optional.
// If a timeout (in milliseconds) is specified, abort execution
// after the timeout has been reached and throw an error.
// For debugging purposes only.
// SuppressErrors (bool) if non-timeout errors occurring in the
// block are handled elsewhere.
// This is highly experimental.
// Caution: Kids, do not try this at home!
// use ThreadManager::startProcess instead
var proc = new Process(block),
startTime = Date.now();
while (proc.isRunning()) {
if (timeout && ((Date.now() - startTime) > timeout)) {
throw (new Error("a synchronous Snap! script has timed out"));
// Use ThreadManager::startProcess with a callback instead
var proc = new Process(),
deadline = (timeout ? Date.now() + timeout : null),
rcvr;
if (action instanceof Context) {
if (receiver) {
action = proc.reportContextFor(receiver);
}
proc.runStep();
proc.initializeFor(action, contextArgs || new List());
} else if (action instanceof BlockMorph) {
proc.topBlock = action;
rcvr = receiver || action.receiver();
if (rcvr) {
proc.homeContext = new Context();
proc.homeContext.receiver = rcvr;
if (rcvr.variables) {
proc.homeContext.variables.parentFrame = rcvr.variables;
}
}
proc.context = new Context(
null,
action.blockSequence(),
proc.homeContext
);
} else {
throw new Error('expecting a block or ring but getting ' + action);
}
if (block instanceof ReporterBlockMorph) {
return proc.homeContext.inputs[0];
if (suppressErrors) {
proc.isCatchingErrors = false;
}
while (proc.isRunning()) {
if (deadline && (Date.now() > deadline)) {
throw (new Error(
localize(
timeoutErrorMsg ||
"a synchronous Snap! script has timed out")
)
);
}
proc.runStep(deadline);
}
return proc.homeContext.inputs[0];
}
// ThreadManager ///////////////////////////////////////////////////////
@ -154,12 +197,14 @@ function ThreadManager() {
this.processes = [];
}
ThreadManager.prototype.pauseCustomHatBlocks = false;
ThreadManager.prototype.toggleProcess = function (block) {
var active = this.findProcess(block);
if (active) {
active.stop();
} else {
return this.startProcess(block);
return this.startProcess(block, null, null, null, true);
}
};
@ -167,7 +212,8 @@ ThreadManager.prototype.startProcess = function (
block,
isThreadSafe,
exportResult,
callback
callback,
isClicked
) {
var active = this.findProcess(block),
top = block.topBlock(),
@ -181,6 +227,7 @@ ThreadManager.prototype.startProcess = function (
}
newProc = new Process(block.topBlock(), callback);
newProc.exportResult = exportResult;
newProc.isClicked = isClicked || false;
if (!newProc.homeContext.receiver.isClone) {
top.addHighlight();
}
@ -267,7 +314,7 @@ ThreadManager.prototype.removeTerminatedProcesses = function () {
}
}
if (proc.topBlock instanceof ReporterBlockMorph) {
if (proc.topBlock instanceof ReporterBlockMorph || proc.isShowingResult) {
if (proc.onComplete instanceof Function) {
proc.onComplete(proc.homeContext.inputs[0]);
} else {
@ -303,6 +350,38 @@ ThreadManager.prototype.findProcess = function (block) {
);
};
ThreadManager.prototype.doWhen = function (block, stopIt) {
if (this.pauseCustomHatBlocks) {return; }
var pred = block.inputs()[0];
if (block.removeHighlight()) {
block.world().hand.destroyTemporaries();
}
if (stopIt) {return; }
if ((!block) ||
!(pred instanceof ReporterBlockMorph) ||
this.findProcess(block)
) {return; }
try {
if (invoke(
pred,
null,
null,
20,
'the predicate takes\ntoo long for a\ncustom hat block',
true // suppress errors => handle them right here instead
) === true) {
this.startProcess(block);
}
} catch (error) {
block.addErrorHighlight();
block.showBubble(
error.name
+ '\n'
+ error.message
);
}
};
// Process /////////////////////////////////////////////////////////////
/*
@ -349,6 +428,10 @@ ThreadManager.prototype.findProcess = function (block) {
httpRequest active instance of an HttpRequest or null
pauseOffset msecs between the start of an interpolated operation
and when the process was paused
isClicked boolean flag indicating whether the process was
initiated by a user-click on a block
isShowingResult boolean flag indicating whether a "report" command
has been executed in a user-clicked process
exportResult boolean flag indicating whether a picture of the top
block along with the result bubble shoud be exported
onComplete an optional callback function to be executed when
@ -369,6 +452,8 @@ function Process(topBlock, onComplete) {
this.readyToYield = false;
this.readyToTerminate = false;
this.isDead = false;
this.isClicked = false;
this.isShowingResult = false;
this.errorFlag = false;
this.context = null;
this.homeContext = new Context();
@ -404,7 +489,7 @@ Process.prototype.isRunning = function () {
// Process entry points
Process.prototype.runStep = function () {
Process.prototype.runStep = function (deadline) {
// a step is an an uninterruptable 'atom', it can consist
// of several contexts, even of several blocks
@ -422,6 +507,14 @@ Process.prototype.runStep = function () {
if (this.isPaused) {
return this.pauseStep();
}
if (deadline && (Date.now() > deadline)) {
if (this.isAtomic &&
this.homeContext.receiver &&
this.homeContext.receiver.endWarp) {
this.homeContext.receiver.endWarp();
}
return;
}
this.evaluateContext();
}
this.lastYield = Date.now();
@ -505,9 +598,12 @@ Process.prototype.evaluateContext = function () {
};
Process.prototype.evaluateBlock = function (block, argCount) {
var selector = block.selector;
// check for special forms
if (contains(['reportOr', 'reportAnd', 'doReport'], block.selector)) {
return this[block.selector](block);
if (selector === 'reportOr' ||
selector === 'reportAnd' ||
selector === 'doReport') {
return this[selector](block);
}
// first evaluate all inputs, then apply the primitive
@ -517,13 +613,13 @@ Process.prototype.evaluateBlock = function (block, argCount) {
if (argCount > inputs.length) {
this.evaluateNextInput(block);
} else {
if (this[block.selector]) {
if (this[selector]) {
rcvr = this;
}
if (this.isCatchingErrors) {
try {
this.returnValueToParentContext(
rcvr[block.selector].apply(rcvr, inputs)
rcvr[selector].apply(rcvr, inputs)
);
this.popContext();
} catch (error) {
@ -531,7 +627,7 @@ Process.prototype.evaluateBlock = function (block, argCount) {
}
} else {
this.returnValueToParentContext(
rcvr[block.selector].apply(rcvr, inputs)
rcvr[selector].apply(rcvr, inputs)
);
this.popContext();
}
@ -574,6 +670,9 @@ Process.prototype.reportAnd = function (block) {
Process.prototype.doReport = function (block) {
var outer = this.context.outerContext;
if (this.isClicked && (block.topBlock() === this.topBlock)) {
this.isShowingResult = true;
}
if (this.context.expression.partOfCustomCommand) {
this.doStopCustomBlock();
this.popContext();
@ -655,10 +754,9 @@ Process.prototype.evaluateInput = function (input) {
} else {
ans = input.evaluate();
if (ans) {
if (contains(
[CommandSlotMorph, ReporterSlotMorph],
input.constructor
) || (input instanceof CSlotMorph &&
if (input.constructor === CommandSlotMorph ||
input.constructor === ReporterSlotMorph ||
(input instanceof CSlotMorph &&
(!input.isStatic || input.isLambda))) {
// I know, this still needs yet to be done right....
ans = this.reify(ans, new List());
@ -718,6 +816,7 @@ Process.prototype.evaluateNextInput = function (element) {
var nxt = this.context.inputs.length,
args = element.inputs(),
exp = args[nxt],
sel = this.context.expression.selector,
outer = this.context.outerContext; // for tail call elimination
if (exp.isUnevaluated) {
@ -729,8 +828,7 @@ Process.prototype.evaluateNextInput = function (element) {
THE SCRIPT), because those allow for additional
explicit parameter bindings.
*/
if (contains(['reify', 'reportScript'],
this.context.expression.selector)) {
if (sel === 'reify' || sel === 'reportScript') {
this.context.addInput(exp);
} else {
this.context.addInput(this.reify(exp, new List()));
@ -944,6 +1042,15 @@ Process.prototype.evaluate = function (
};
Process.prototype.fork = function (context, args) {
var proc = new Process(),
stage = this.homeContext.receiver.parentThatIsA(StageMorph);
proc.initializeFor(context, args);
proc.pushContext('doYield');
stage.threads.processes.push(proc);
};
Process.prototype.initializeFor = function (context, args) {
// used by Process.fork() and global invoke()
if (context.isContinuation) {
throw new Error(
'continuations cannot be forked'
@ -961,8 +1068,7 @@ Process.prototype.fork = function (context, args) {
parms = args.asArray(),
i,
value,
stage = this.homeContext.receiver.parentThatIsA(StageMorph),
proc = new Process();
exit;
// assign parameters if any were passed
if (parms.length > 0) {
@ -1008,13 +1114,24 @@ Process.prototype.fork = function (context, args) {
if (runnable.expression instanceof CommandBlockMorph) {
runnable.expression = runnable.expression.blockSequence();
// insert a tagged exit context
// which "report" can catch later
// needed for invoke() situations
exit = new Context(
runnable.parentContext,
'expectReport',
outer,
outer.receiver
);
exit.tag = 'exit';
runnable.parentContext = exit;
}
proc.homeContext = context.outerContext;
proc.topBlock = context.expression;
proc.context = runnable;
proc.pushContext('doYield');
stage.threads.processes.push(proc);
this.homeContext = new Context(); // context.outerContext;
this.homeContext.receiver = context.outerContext.receiver;
this.topBlock = context.expression;
this.context = runnable;
};
// Process stopping blocks primitives
@ -1091,8 +1208,21 @@ Process.prototype.evaluateCustomBlock = function () {
this.procedureCount += 1;
outer = new Context();
outer.receiver = this.context.receiver;
outer.variables.parentFrame = outer.receiver ?
outer.receiver.variables : null;
outer.variables.parentFrame = this.context.expression.variables;
// block (instance) var support, experimental:
// only splice in block vars if any are defined, because block vars
// can cause race conditions in global block definitions that
// access sprite-local variables at the same time.
if (this.context.expression.definition.variableNames.length) {
this.context.expression.variables.parentFrame = outer.receiver ?
outer.receiver.variables : null;
} else {
// original code without block variables:
outer.variables.parentFrame = outer.receiver ?
outer.receiver.variables : null;
}
runnable = new Context(
this.context.parentContext,
@ -1420,7 +1550,10 @@ Process.prototype.reportListItem = function (index, list) {
};
Process.prototype.reportListLength = function (list) {
return list.length();
if (list instanceof List) {
return list.length();
}
return list.length; // catch a common student error
};
Process.prototype.reportListContainsItem = function (list, element) {
@ -2110,10 +2243,12 @@ Process.prototype.reportIsIdentical = function (a, b) {
Process.prototype.isImmutable = function (obj) {
// private
return contains(
['nothing', 'Boolean', 'text', 'number', 'undefined'],
this.reportTypeOf(obj)
);
var type = this.reportTypeOf(obj);
return type === 'nothing' ||
type === 'Boolean' ||
type === 'text' ||
type === 'number' ||
type === 'undefined';
};
Process.prototype.reportTrue = function () {