let clones share the orginal’s scripts without shallow-copying them

upd4.1
Jens Mönig 2017-05-30 17:07:09 +02:00
rodzic cc47a6fb5a
commit eea1d08c82
6 zmienionych plików z 228 dodań i 181 usunięć

160
blocks.js
Wyświetl plik

@ -150,7 +150,7 @@ CustomCommandBlockMorph*/
// Global stuff ////////////////////////////////////////////////////////
modules.blocks = '2017-May-12';
modules.blocks = '2017-May-30';
var SyntaxElementMorph;
var BlockMorph;
@ -570,7 +570,7 @@ SyntaxElementMorph.prototype.revertToDefaultInput = function (arg, noValues) {
deflt = this.labelPart(this.parseSpec(this.blockSpec)[idx]);
if (this.isCustomBlock) {
def = this.isGlobal ? this.definition
: this.receiver().getMethod(this.blockSpec);
: this.scriptTarget().getMethod(this.blockSpec);
if (deflt instanceof InputSlotMorph) {
deflt.setChoices.apply(
deflt,
@ -636,7 +636,7 @@ SyntaxElementMorph.prototype.getVarNamesDict = function () {
if (!block) {
return {};
}
rcvr = block.receiver();
rcvr = block.scriptTarget();
block.allParents().forEach(function (morph) {
if (morph instanceof PrototypeHatBlockMorph) {
tempVars.push.apply(
@ -1546,7 +1546,7 @@ SyntaxElementMorph.prototype.labelPart = function (spec) {
// has issues when loading costumes (asynchronously)
// commented out for now
var rcvr = this.definition.receiver || this.receiver(),
var rcvr = this.definition.receiver || this.scriptTarget(),
id = spec.slice(1),
cst;
if (!rcvr) {return this.labelPart('%stop'); }
@ -1888,7 +1888,7 @@ SyntaxElementMorph.prototype.showBubble = function (value, exportPic) {
if ((value === undefined) || !wrrld || !this.receiver) {
return null;
}
rcvr = this.receiver();
rcvr = this.scriptTarget();
if (value instanceof ListWatcherMorph) {
morphToShow = value;
morphToShow.update(true);
@ -2053,7 +2053,7 @@ SyntaxElementMorph.prototype.endLayout = function () {
accessors are:
selector - (string) name of method to be triggered
receiver() - answer the object (sprite) to which I apply
scriptTarget() - answer the object (sprite) to which I apply
inputs() - answer an array with my arg slots and nested reporters
defaults - an optional Array containing default input values
topBlock() - answer the top block of the stack I'm attached to
@ -2204,16 +2204,23 @@ BlockMorph.prototype.init = function (silently) {
this.cachedInputs = null;
};
BlockMorph.prototype.receiver = function () {
// answer the object to which I apply (whose method I represent)
var up = this.parent;
while (!!up) {
if (up.owner) {
return up.owner;
}
up = up.parent;
BlockMorph.prototype.scriptTarget = function () {
// answer the sprite or stage that this block acts on,
// if the user clicks on it.
// NOTE: since scripts can be shared by more than a single sprite
// this method only gives the desired result within the context of
// the user actively clicking on a block inside the IDE
// there is no direct relationship between a block and a sprite.
var scripts = this.parentThatIsA(ScriptsMorph),
ide;
if (scripts) {
return scripts.scriptTarget();
}
return null;
ide = this.parentThatIsA(IDE_Morph);
if (ide) {
return ide.currentSprite;
}
throw new Error('script target cannot be found for orphaned block');
};
BlockMorph.prototype.toString = function () {
@ -2461,7 +2468,7 @@ BlockMorph.prototype.userMenu = function () {
// allow toggling inheritable attributes
if (StageMorph.prototype.enableInheritance) {
rcvr = this.receiver();
rcvr = this.scriptTarget();
field = {
xPosition: 'x position',
yPosition: 'y position',
@ -2695,7 +2702,7 @@ BlockMorph.prototype.isInheritedVariable = function () {
(this.selector === 'reportGetVar') &&
(this.parent instanceof FrameMorph)) {
return contains(
this.receiver().inheritedVariableNames(),
this.scriptTarget().inheritedVariableNames(),
this.blockSpec
);
}
@ -2704,13 +2711,13 @@ BlockMorph.prototype.isInheritedVariable = function () {
BlockMorph.prototype.isTransientVariable = function () {
// private - only for variable getter template inside the palette
var varFrame = this.receiver().variables.silentFind(this.blockSpec);
var varFrame = this.scriptTarget().variables.silentFind(this.blockSpec);
return varFrame ? varFrame.vars[this.blockSpec].isTransient : false;
};
BlockMorph.prototype.toggleTransientVariable = function () {
// private - only for variable getter template inside the palette
var varFrame = this.receiver().variables.silentFind(this.blockSpec);
var varFrame = this.scriptTarget().variables.silentFind(this.blockSpec);
if (!varFrame) {return; }
varFrame.vars[this.blockSpec].isTransient =
!(varFrame.vars[this.blockSpec].isTransient);
@ -3212,7 +3219,7 @@ BlockMorph.prototype.refactorThisVar = function (justTheTemplate) {
var myself = this,
oldName = this.blockSpec,
receiver = this.receiver(),
receiver = this.scriptTarget(),
ide = this.parentThatIsA(IDE_Morph),
stage = ide.stage,
oldWatcher = receiver.findVariableWatcher(oldName),
@ -3674,14 +3681,7 @@ BlockMorph.prototype.hasLabels = function () {
// BlockMorph copying
BlockMorph.prototype.fullCopy = function (forClone) {
if (forClone) {
if (this.hasBlockVars()) {
forClone = false;
} else {
return copy(this);
}
}
BlockMorph.prototype.fullCopy = function () {
var ans = BlockMorph.uber.fullCopy.call(this);
ans.removeHighlight();
ans.isDraggable = true;
@ -3721,7 +3721,7 @@ BlockMorph.prototype.hasBlockVars = function () {
BlockMorph.prototype.mouseClickLeft = function () {
var top = this.topBlock(),
receiver = top.receiver(),
receiver = top.scriptTarget(),
shiftClicked = this.world().currentKey === 16,
stage;
if (shiftClicked && !this.isTemplate) {
@ -3733,7 +3733,7 @@ BlockMorph.prototype.mouseClickLeft = function () {
if (receiver) {
stage = receiver.parentThatIsA(StageMorph);
if (stage) {
stage.threads.toggleProcess(top);
stage.threads.toggleProcess(top, receiver);
}
}
};
@ -3755,7 +3755,7 @@ BlockMorph.prototype.focus = function () {
BlockMorph.prototype.activeProcess = function () {
var top = this.topBlock(),
receiver = top.receiver(),
receiver = top.scriptTarget(),
stage;
if (top instanceof PrototypeHatBlockMorph) {
return null;
@ -3763,7 +3763,7 @@ BlockMorph.prototype.activeProcess = function () {
if (receiver) {
stage = receiver.parentThatIsA(StageMorph);
if (stage) {
return stage.threads.findProcess(top);
return stage.threads.findProcess(top, receiver);
}
}
return null;
@ -3908,10 +3908,16 @@ BlockMorph.prototype.destroy = function (justThis) {
comment.destroy();
});
}
/* +++ needs tweaking:
// stop active process(es) for this block
// for this we need access to the stage...
if ((!this.parent || !this.parent.topBlock)
&& this.activeProcess()) {
this.activeProcess().stop();
}
*/
BlockMorph.uber.destroy.call(this);
};
@ -3957,7 +3963,7 @@ BlockMorph.prototype.snap = function () {
}
// register generic hat blocks
if (this.selector === 'receiveCondition') {
receiver = top.receiver();
receiver = top.scriptTarget();
if (receiver) {
stage = receiver.parentThatIsA(StageMorph);
if (stage) {
@ -5156,14 +5162,14 @@ ReporterBlockMorph.prototype.mouseClickLeft = function (pos) {
ReporterBlockMorph.prototype.exportResultPic = function () {
var top = this.topBlock(),
receiver = top.receiver(),
receiver = top.scriptTarget(),
stage;
if (top !== this) {return; }
if (receiver) {
stage = receiver.parentThatIsA(StageMorph);
if (stage) {
stage.threads.stopProcess(top);
stage.threads.startProcess(top, false, true);
stage.threads.startProcess(top, receiver, false, true);
}
}
};
@ -5711,12 +5717,11 @@ ScriptsMorph.prototype.enableNestedAutoWrapping = true;
// ScriptsMorph instance creation:
function ScriptsMorph(owner) {
this.init(owner);
function ScriptsMorph() {
this.init();
}
ScriptsMorph.prototype.init = function (owner) {
this.owner = owner || null;
ScriptsMorph.prototype.init = function () {
this.feedbackColor = SyntaxElementMorph.prototype.feedbackColor;
this.feedbackMorph = new BoxMorph();
this.rejectsHats = false;
@ -5744,7 +5749,7 @@ ScriptsMorph.prototype.init = function (owner) {
// ScriptsMorph deep copying:
ScriptsMorph.prototype.fullCopy = function (forClone) {
ScriptsMorph.prototype.fullCopy = function () {
var cpy = new ScriptsMorph(),
pos = this.position(),
child;
@ -5753,21 +5758,17 @@ ScriptsMorph.prototype.fullCopy = function (forClone) {
}
this.children.forEach(function (morph) {
if (!morph.block) { // omit anchored comments
child = morph.fullCopy(forClone);
child = morph.fullCopy();
cpy.add(child);
if (!forClone) {
child.setPosition(morph.position().subtract(pos));
if (child instanceof BlockMorph) {
child.allComments().forEach(function (comment) {
comment.align(child);
});
}
child.setPosition(morph.position().subtract(pos));
if (child instanceof BlockMorph) {
child.allComments().forEach(function (comment) {
comment.align(child);
});
}
}
});
if (!forClone) {
cpy.adjustBounds();
}
cpy.adjustBounds();
return cpy;
};
@ -6073,7 +6074,7 @@ ScriptsMorph.prototype.userMenu = function () {
shiftClicked = this.world().currentKey === 16,
blockEditor,
myself = this,
obj = this.owner,
obj = this.scriptTarget(),
hasUndropQueue,
stage = obj.parentThatIsA(StageMorph);
@ -6594,6 +6595,28 @@ ScriptsMorph.prototype.edit = function (pos) {
this.focus.getFocus(world);
};
// ScriptsMorph context - scripts target
ScriptsMorph.prototype.scriptTarget = function () {
// answer the sprite or stage that this script editor acts on,
// if the user clicks on a block.
// NOTE: since scripts can be shared by more than a single sprite
// this method only gives the desired result within the context of
// the user actively clicking on a block inside the IDE
// there is no direct relationship between a block or a scripts editor
// and a sprite.
var editor = this.parentThatIsA(IDE_Morph);
if (editor) {
return editor.currentSprite;
}
editor = this.parentThatIsA(BlockEditorMorph);
if (editor) {
return editor.target.currentSprite;
}
throw new Error('script target bannot be found for orphaned scripts');
};
// ArgMorph //////////////////////////////////////////////////////////
/*
@ -6644,7 +6667,7 @@ ArgMorph.prototype.reactToSliderEdit = function () {
block = this.parentThatIsA(BlockMorph);
if (block) {
top = block.topBlock();
receiver = top.receiver();
receiver = top.scriptTarget();
if (top instanceof PrototypeHatBlockMorph) {
return;
}
@ -6652,7 +6675,7 @@ ArgMorph.prototype.reactToSliderEdit = function () {
stage = receiver.parentThatIsA(StageMorph);
if (stage && (stage.isThreadSafe ||
Process.prototype.enableSingleStepping)) {
stage.threads.startProcess(top, stage.isThreadSafe);
stage.threads.startProcess(top, receiver, stage.isThreadSafe);
} else {
top.mouseClickLeft();
}
@ -7944,7 +7967,7 @@ InputSlotMorph.prototype.menuFromDict = function (choices, noEmptyOption) {
InputSlotMorph.prototype.messagesMenu = function () {
var dict = {},
rcvr = this.parentThatIsA(BlockMorph).receiver(),
rcvr = this.parentThatIsA(BlockMorph).scriptTarget(),
stage = rcvr.parentThatIsA(StageMorph),
myself = this,
allNames = [];
@ -7977,7 +8000,7 @@ InputSlotMorph.prototype.messagesMenu = function () {
InputSlotMorph.prototype.messagesReceivedMenu = function () {
var dict = {'any message': ['any message']},
rcvr = this.parentThatIsA(BlockMorph).receiver(),
rcvr = this.parentThatIsA(BlockMorph).scriptTarget(),
stage = rcvr.parentThatIsA(StageMorph),
myself = this,
allNames = [];
@ -8012,7 +8035,7 @@ InputSlotMorph.prototype.collidablesMenu = function () {
edge : ['edge'],
'pen trails' : ['pen trails']
},
rcvr = this.parentThatIsA(BlockMorph).receiver(),
rcvr = this.parentThatIsA(BlockMorph).scriptTarget(),
stage = rcvr.parentThatIsA(StageMorph),
allNames = [];
@ -8036,7 +8059,7 @@ InputSlotMorph.prototype.distancesMenu = function () {
var dict = {
'mouse-pointer' : ['mouse-pointer']
},
rcvr = this.parentThatIsA(BlockMorph).receiver(),
rcvr = this.parentThatIsA(BlockMorph).scriptTarget(),
stage = rcvr.parentThatIsA(StageMorph),
allNames = [];
@ -8058,7 +8081,7 @@ InputSlotMorph.prototype.distancesMenu = function () {
InputSlotMorph.prototype.clonablesMenu = function () {
var dict = {},
rcvr = this.parentThatIsA(BlockMorph).receiver(),
rcvr = this.parentThatIsA(BlockMorph).scriptTarget(),
stage = rcvr.parentThatIsA(StageMorph),
allNames = [];
@ -8080,7 +8103,7 @@ InputSlotMorph.prototype.clonablesMenu = function () {
};
InputSlotMorph.prototype.objectsMenu = function () {
var rcvr = this.parentThatIsA(BlockMorph).receiver(),
var rcvr = this.parentThatIsA(BlockMorph).scriptTarget(),
stage = rcvr.parentThatIsA(StageMorph),
dict = {},
allNames = [];
@ -8146,7 +8169,7 @@ InputSlotMorph.prototype.gettablesMenu = function () {
InputSlotMorph.prototype.attributesMenu = function () {
var block = this.parentThatIsA(BlockMorph),
objName = block.inputs()[1].evaluate(),
rcvr = block.receiver(),
rcvr = block.scriptTarget(),
stage = rcvr.parentThatIsA(StageMorph),
obj,
dict = {},
@ -8196,7 +8219,7 @@ InputSlotMorph.prototype.attributesMenu = function () {
};
InputSlotMorph.prototype.costumesMenu = function () {
var rcvr = this.parentThatIsA(BlockMorph).receiver(),
var rcvr = this.parentThatIsA(BlockMorph).scriptTarget(),
dict,
allNames = [];
if (rcvr instanceof SpriteMorph) {
@ -8217,7 +8240,7 @@ InputSlotMorph.prototype.costumesMenu = function () {
};
InputSlotMorph.prototype.soundsMenu = function () {
var rcvr = this.parentThatIsA(BlockMorph).receiver(),
var rcvr = this.parentThatIsA(BlockMorph).scriptTarget(),
allNames = [],
dict = {};
@ -8257,7 +8280,7 @@ InputSlotMorph.prototype.shadowedVariablesMenu = function () {
dict = {};
if (!block) {return dict; }
rcvr = block.receiver();
rcvr = block.scriptTarget();
if (rcvr && rcvr.exemplar) {
vars = rcvr.inheritedVariableNames(true);
vars.forEach(function (name) {
@ -13237,7 +13260,7 @@ ScriptFocusMorph.prototype.deleteLastElement = function () {
};
ScriptFocusMorph.prototype.insertBlock = function (block) {
var pb, stage, ide;
var pb, stage, ide, rcvr;
block.isTemplate = false;
block.isDraggable = true;
@ -13303,8 +13326,9 @@ ScriptFocusMorph.prototype.insertBlock = function (block) {
// register generic hat blocks
if (block.selector === 'receiveCondition') {
if (this.editor.owner) {
stage = this.editor.owner.parentThatIsA(StageMorph);
rcvr = this.editor.scriptTarget();
if (rcvr) {
stage = rcvr.parentThatIsA(StageMorph);
if (stage) {
stage.enableCustomHatBlocks = true;
stage.threads.pauseCustomHatBlocks = false;
@ -13752,7 +13776,7 @@ ScriptFocusMorph.prototype.reactToKeyEvent = function (key) {
delete this.fps;
delete this.step;
this.show();
this.editor.owner.searchBlocks(
this.editor.scriptTarget().searchBlocks(
key,
types,
vNames,

23
byob.js
Wyświetl plik

@ -108,7 +108,7 @@ BooleanSlotMorph, XML_Serializer*/
// Global stuff ////////////////////////////////////////////////////////
modules.byob = '2017-April-10';
modules.byob = '2017-May-30';
// Declarations
@ -799,7 +799,7 @@ CustomCommandBlockMorph.prototype.edit = function () {
);
} else {
// check for local custom block inheritance
rcvr = this.receiver();
rcvr = this.scriptTarget();
if (!this.isGlobal) {
if (contains(
Object.keys(rcvr.inheritedBlocks()),
@ -860,8 +860,12 @@ CustomCommandBlockMorph.prototype.attachTargets = function () {
CustomCommandBlockMorph.prototype.isInUse = function () {
// answer true if an instance of my definition is found
// in any of my receiver's scripts or block definitions
// NOTE: for sprite-local blocks only to be used in a situation
// where the user actively clicks on a block in the IDE,
// e.g. to edit it (and change its type)
var def = this.definition,
ide = this.receiver().parentThatIsA(IDE_Morph);
rcvr = this.scriptTarget(),
ide = rcvr.parentThatIsA(IDE_Morph);
if (def.isGlobal && ide) {
return ide.sprites.asArray().concat([ide.stage]).some(
function (any, idx) {
@ -869,15 +873,14 @@ CustomCommandBlockMorph.prototype.isInUse = function () {
}
);
}
// return this.receiver().usesBlockInstance(def);
return this.receiver().allDependentInvocationsOf(this.blockSpec).length > 0;
return rcvr.allDependentInvocationsOf(this.blockSpec).length > 0;
};
// CustomCommandBlockMorph menu:
CustomCommandBlockMorph.prototype.userMenu = function () {
var hat = this.parentThatIsA(PrototypeHatBlockMorph),
rcvr = this.receiver(),
rcvr = this.scriptTarget(),
myself = this,
shiftClicked = this.world().currentKey === 16,
menu;
@ -999,7 +1002,7 @@ CustomCommandBlockMorph.prototype.exportBlockDefinition = function () {
};
CustomCommandBlockMorph.prototype.duplicateBlockDefinition = function () {
var rcvr = this.receiver(),
var rcvr = this.scriptTarget(),
ide = this.parentThatIsA(IDE_Morph),
def = this.isGlobal ? this.definition : rcvr.getMethod(this.blockSpec),
dup = def.copyAndBindTo(rcvr);
@ -1015,7 +1018,7 @@ CustomCommandBlockMorph.prototype.duplicateBlockDefinition = function () {
CustomCommandBlockMorph.prototype.deleteBlockDefinition = function () {
var idx, stage, ide, method, block,
rcvr = this.receiver(),
rcvr = this.scriptTarget(),
myself = this;
if (this.isPrototype) {
return null; // under construction...
@ -1093,7 +1096,7 @@ CustomCommandBlockMorph.prototype.relabel = function (alternatives) {
};
CustomCommandBlockMorph.prototype.alternatives = function () {
var rcvr = this.receiver(),
var rcvr = this.scriptTarget(),
stage = rcvr.parentThatIsA(StageMorph),
allDefs = rcvr.customBlocks.concat(stage.globalBlocks),
type = this instanceof CommandBlockMorph ? 'command'
@ -1894,7 +1897,7 @@ BlockEditorMorph.prototype.init = function (definition, target) {
this.createLabel();
// create scripting area
scripts = new ScriptsMorph(target);
scripts = new ScriptsMorph();
scripts.rejectsHats = true;
scripts.isDraggable = false;
scripts.color = IDE_Morph.prototype.groupColor;

2
gui.js
Wyświetl plik

@ -74,7 +74,7 @@ isRetinaSupported, SliderMorph, Animation*/
// Global stuff ////////////////////////////////////////////////////////
modules.gui = '2017-May-12';
modules.gui = '2017-May-30';
// Declarations

Wyświetl plik

@ -3428,6 +3428,10 @@ Fixes:
* added inheritance support for the wardrobe (costumes)
* added inheritance support for costume #
170530
------
* let clones share the orginals scripts without shallow-copying them
Features:
* polymorphic sprite-local custom blocks
@ -3441,6 +3445,7 @@ Features:
* support for codification of String, Number and Boolean value types
* costume icons indicate svg costumes
* spritess rotation centers can be adjusted onstage
* clones share their original sprites scripts, not a shallow-copy of them
Fixes:
* changed keyboard shortcut indicator for “find blocks” to “^”

Wyświetl plik

@ -82,7 +82,7 @@ SpeechBubbleMorph, RingMorph, isNil, FileReader, TableDialogMorph,
BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, localize,
TableMorph, TableFrameMorph, normalizeCanvas, BooleanSlotMorph, HandleMorph*/
modules.objects = '2017-May-15';
modules.objects = '2017-May-30';
var SpriteMorph;
var StageMorph;
@ -1343,7 +1343,7 @@ function SpriteMorph(globals) {
SpriteMorph.prototype.init = function (globals) {
this.name = localize('Sprite');
this.variables = new VariableFrame(globals || null, this);
this.scripts = new ScriptsMorph(this);
this.scripts = new ScriptsMorph();
this.customBlocks = [];
this.costumes = new List();
this.costume = null;
@ -1388,7 +1388,7 @@ SpriteMorph.prototype.init = function (globals) {
// sprite inheritance
this.exemplar = null;
this.cachedSpecimens = null; // temporary for morphic animations
this.cachedSpecimens = null; // not to be persisted
this.inheritedAttributes = []; // 'x position', 'direction', 'size' etc...
SpriteMorph.uber.init.call(this);
@ -1413,11 +1413,10 @@ SpriteMorph.prototype.fullCopy = function (forClone) {
c.color = this.color.copy();
c.blocksCache = {};
c.paletteCache = {};
c.scripts = this.scripts.fullCopy(forClone);
c.scripts.owner = c;
c.variables = this.variables.copy();
c.variables.owner = c;
if (!forClone) {
c.scripts = this.scripts.fullCopy();
c.customBlocks = [];
this.customBlocks.forEach(function (def) {
cb = def.copyAndBindTo(c);
@ -3159,7 +3158,8 @@ SpriteMorph.prototype.createClone = function (immediately) {
};
SpriteMorph.prototype.clonify = function (stage, immediately) {
var hats;
var hats,
myself = this;
this.parts.forEach(function (part) {
part.clonify(stage);
});
@ -3173,6 +3173,7 @@ SpriteMorph.prototype.clonify = function (stage, immediately) {
hats.forEach(function (block) {
stage.threads.startProcess(
block,
myself,
stage.isThreadSafe,
null, // export result
null, // callback
@ -3899,7 +3900,7 @@ SpriteMorph.prototype.prepareToBeGrabbed = function (hand) {
this.recordLayers();
this.shadowAttribute('x position');
this.shadowAttribute('y position');
this.cachedSpecimens = this.specimens();
this.specimens(); // make sure specimens are cached
if (!this.bounds.containsPoint(hand.position()) &&
this.isCorrectingOutsideDrag()) {
this.setCenter(hand.position());
@ -3920,7 +3921,6 @@ SpriteMorph.prototype.justDropped = function () {
if (stage) {
stage.enableCustomHatBlocks = true;
}
this.cachedSpecimens = null;
if (this.exemplar) {
this.inheritedAttributes.forEach(function (att) {
myself.refreshInheritedAttribute(att);
@ -4111,17 +4111,17 @@ SpriteMorph.prototype.slideBackTo = function (
var myself = this;
// caching specimens
this.cachedSpecimens = situation.origin.children.filter(function (m) {
return m instanceof SpriteMorph && (m.exemplar === myself);
});
if (isNil(this.cachedSpecimens)) {
this.cachedSpecimens = situation.origin.children.filter(function (m) {
return m instanceof SpriteMorph && (m.exemplar === myself);
});
}
SpriteMorph.uber.slideBackTo.call(
this,
situation,
msecs,
function () {
// make sure to flush cached specimens
myself.cachedSpecimens = null;
if (onBeforeDrop) {onBeforeDrop(); }
},
onBeforeDrop,
@ -4548,11 +4548,16 @@ SpriteMorph.prototype.mouseDownLeft = function () {
SpriteMorph.prototype.receiveUserInteraction = function (interaction) {
var stage = this.parentThatIsA(StageMorph),
procs = [],
myself = this,
hats;
if (!stage) {return; } // currently dragged
hats = this.allHatBlocksForInteraction(interaction);
hats.forEach(function (block) {
procs.push(stage.threads.startProcess(block, stage.isThreadSafe));
procs.push(stage.threads.startProcess(
block,
myself,
stage.isThreadSafe
));
});
return procs;
};
@ -5129,6 +5134,7 @@ SpriteMorph.prototype.setExemplar = function (another) {
ide.flushBlocksCache('variables');
ide.refreshPalette();
}
another.cachedSpecimens = null;
};
SpriteMorph.prototype.allExemplars = function () {
@ -5145,9 +5151,12 @@ SpriteMorph.prototype.allExemplars = function () {
SpriteMorph.prototype.specimens = function () {
// without myself
var myself = this;
return this.cachedSpecimens || this.siblings().filter(function (m) {
return m instanceof SpriteMorph && (m.exemplar === myself);
});
if (isNil(this.cachedSpecimens)) {
this.cachedSpecimens = this.siblings().filter(function (m) {
return m instanceof SpriteMorph && (m.exemplar === myself);
});
}
return this.cachedSpecimens;
};
SpriteMorph.prototype.allSpecimens = function () {
@ -5536,6 +5545,16 @@ SpriteMorph.prototype.restoreLayers = function () {
this.layers = null;
};
// SpriteMorph destroying
SpriteMorph.prototype.destroy = function () {
// make sure to invalidate the specimens cache
if (this.exemplar) {
this.exemplar.cachedSpecimens = null;
}
SpriteMorph.uber.destroy.call(this);
};
// SpriteMorph highlighting
SpriteMorph.prototype.addHighlight = function (oldHighlight) {
@ -5768,7 +5787,7 @@ StageMorph.prototype.init = function (globals) {
this.name = localize('Stage');
this.threads = new ThreadManager();
this.variables = new VariableFrame(globals || null, this);
this.scripts = new ScriptsMorph(this);
this.scripts = new ScriptsMorph();
this.customBlocks = [];
this.globalBlocks = [];
this.costumes = new List();
@ -6174,25 +6193,24 @@ StageMorph.prototype.step = function () {
};
StageMorph.prototype.stepGenericConditions = function (stopAll) {
var hats = [],
var hatCount = 0,
myself = this,
ide;
this.children.concat(this).forEach(function (morph) {
if (morph instanceof SpriteMorph || morph instanceof StageMorph) {
hats = hats.concat(morph.allGenericHatBlocks());
if (isSnapObject(morph)) {
morph.allGenericHatBlocks().forEach(function (block) {
hatCount += 1;
myself.threads.doWhen(block, morph, stopAll);
});
}
});
if (!hats.length) {
if (!hatCount) {
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 () {
@ -6266,7 +6284,6 @@ StageMorph.prototype.processKeyEvent = function (event, action) {
StageMorph.prototype.fireKeyEvent = function (key) {
var evt = key.toLowerCase(),
hats = [],
procs = [],
ide = this.parentThatIsA(IDE_Morph),
myself = this;
@ -6311,12 +6328,15 @@ StageMorph.prototype.fireKeyEvent = function (key) {
}
this.children.concat(this).forEach(function (morph) {
if (isSnapObject(morph)) {
hats = hats.concat(morph.allHatBlocksForKey(evt));
morph.allHatBlocksForKey(evt).forEach(function (block) {
procs.push(myself.threads.startProcess(
block,
morph,
myself.isThreadSafe
));
});
}
});
hats.forEach(function (block) {
procs.push(myself.threads.startProcess(block, myself.isThreadSafe));
});
return procs;
};
@ -6333,21 +6353,20 @@ StageMorph.prototype.inspectKeyEvent
StageMorph.prototype.fireGreenFlagEvent = function () {
var procs = [],
hats = [],
ide = this.parentThatIsA(IDE_Morph),
myself = this;
this.children.concat(this).forEach(function (morph) {
if (isSnapObject(morph)) {
hats = hats.concat(morph.allHatBlocksFor('__shout__go__'));
morph.allHatBlocksFor('__shout__go__').forEach(function (block) {
procs.push(myself.threads.startProcess(
block,
morph,
myself.isThreadSafe
));
});
}
});
hats.forEach(function (block) {
procs.push(myself.threads.startProcess(
block,
myself.isThreadSafe
));
});
if (ide) {
ide.controlBar.pauseButton.refresh();
}
@ -7210,6 +7229,12 @@ StageMorph.prototype.allSpecimens = function () {
StageMorph.prototype.shadowAttribute = nop;
// StageMorph inheritance support - attributes
StageMorph.prototype.inheritsAttribute = function () {
return false;
};
// StageMorph inheritance support - variables
StageMorph.prototype.isVariableNameInUse

Wyświetl plik

@ -61,7 +61,7 @@ StageMorph, SpriteMorph, StagePrompterMorph, Note, modules, isString, copy,
isNil, WatcherMorph, List, ListWatcherMorph, alert, console, TableMorph,
TableFrameMorph, ColorSlotMorph, isSnapObject*/
modules.threads = '2017-May-12';
modules.threads = '2017-May-30';
var ThreadManager;
var Process;
@ -109,7 +109,7 @@ function snapEquals(a, b) {
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
receiver, // sprite or environment, optional for contexts
timeout, // msecs
timeoutErrorMsg, // string
suppressErrors // bool
@ -126,23 +126,23 @@ function invoke(
// Use ThreadManager::startProcess with a callback instead
var proc = new Process(),
deadline = (timeout ? Date.now() + timeout : null),
rcvr;
deadline = (timeout ? Date.now() + timeout : null);
if (action instanceof Context) {
if (receiver) {
if (receiver) { // optional
action = proc.reportContextFor(receiver);
}
proc.initializeFor(action, contextArgs || new List());
} else if (action instanceof BlockMorph) {
proc.topBlock = action;
rcvr = receiver || action.receiver();
if (rcvr) {
if (receiver) {
proc.homeContext = new Context();
proc.homeContext.receiver = rcvr;
if (rcvr.variables) {
proc.homeContext.variables.parentFrame = rcvr.variables;
proc.homeContext.receiver = receiver;
if (receiver.variables) {
proc.homeContext.variables.parentFrame = receiver.variables;
}
} else {
throw new Error('expecting a receiver but getting ' + receiver);
}
proc.context = new Context(
null,
@ -180,25 +180,26 @@ function ThreadManager() {
ThreadManager.prototype.pauseCustomHatBlocks = false;
ThreadManager.prototype.toggleProcess = function (block) {
var active = this.findProcess(block);
ThreadManager.prototype.toggleProcess = function (block, receiver) {
var active = this.findProcess(block, receiver);
if (active) {
active.stop();
} else {
return this.startProcess(block, null, null, null, true);
return this.startProcess(block, receiver, null, null, null, true);
}
};
ThreadManager.prototype.startProcess = function (
block,
receiver,
isThreadSafe,
exportResult,
exportResult, // bool
callback,
isClicked,
rightAway
) {
var active = this.findProcess(block),
top = block.topBlock(),
var top = block.topBlock(),
active = this.findProcess(top, receiver),
newProc;
if (active) {
if (isThreadSafe) {
@ -207,7 +208,7 @@ ThreadManager.prototype.startProcess = function (
active.stop();
this.removeTerminatedProcesses();
}
newProc = new Process(block.topBlock(), callback, rightAway);
newProc = new Process(top, receiver, callback, rightAway);
newProc.exportResult = exportResult;
newProc.isClicked = isClicked || false;
if (!newProc.homeContext.receiver.isClone) {
@ -241,8 +242,8 @@ ThreadManager.prototype.stopAllForReceiver = function (rcvr, excpt) {
});
};
ThreadManager.prototype.stopProcess = function (block) {
var active = this.findProcess(block);
ThreadManager.prototype.stopProcess = function (block, receiver) {
var active = this.findProcess(block, receiver);
if (active) {
active.stop();
}
@ -309,7 +310,8 @@ ThreadManager.prototype.removeTerminatedProcesses = function () {
this.processes.forEach(function (proc) {
var result;
if ((!proc.isRunning() && !proc.errorFlag) || proc.isDead) {
if (proc.topBlock instanceof BlockMorph) {
if (proc.topBlock instanceof BlockMorph
&& (!proc.receiver.isClone)) {
proc.unflash();
proc.topBlock.removeHighlight();
}
@ -349,19 +351,19 @@ ThreadManager.prototype.removeTerminatedProcesses = function () {
this.processes = remaining;
};
ThreadManager.prototype.findProcess = function (block) {
ThreadManager.prototype.findProcess = function (block, receiver) {
var top = block.topBlock();
return detect(
this.processes,
function (each) {
return each.topBlock === top;
return each.topBlock === top && (each.receiver === receiver);
}
);
};
ThreadManager.prototype.doWhen = function (block, stopIt) {
ThreadManager.prototype.doWhen = function (block, receiver, stopIt) {
if (this.pauseCustomHatBlocks) {return; }
if ((!block) || this.findProcess(block)) {
if ((!block) || this.findProcess(block, receiver)) {
return;
}
var pred = block.inputs()[0], world;
@ -376,12 +378,20 @@ ThreadManager.prototype.doWhen = function (block, stopIt) {
if (invoke(
pred,
null,
block.receiver(), // needed for shallow copied clones - was null
receiver,
50,
'the predicate takes\ntoo long for a\ncustom hat block',
true // suppress errors => handle them right here instead
) === true) {
this.startProcess(block, null, null, null, null, true); // atomic
this.startProcess(
block,
receiver,
null,
null,
null,
null,
true // atomic
);
}
} catch (error) {
block.addErrorHighlight();
@ -476,9 +486,9 @@ Process.prototype.enableSingleStepping = false; // experimental
Process.prototype.flashTime = 0; // experimental
// Process.prototype.enableJS = false;
function Process(topBlock, onComplete, rightAway) {
function Process(topBlock, receiver, onComplete, rightAway) {
this.topBlock = topBlock || null;
this.receiver = receiver;
this.readyToYield = false;
this.readyToTerminate = false;
this.isDead = false;
@ -486,7 +496,7 @@ function Process(topBlock, onComplete, rightAway) {
this.isShowingResult = false;
this.errorFlag = false;
this.context = null;
this.homeContext = new Context();
this.homeContext = new Context(null, null, null, receiver);
this.lastYield = Date.now();
this.isFirstStep = true;
this.isAtomic = false;
@ -502,7 +512,6 @@ function Process(topBlock, onComplete, rightAway) {
this.isInterrupted = false; // experimental, for single-stepping
if (topBlock) {
this.homeContext.receiver = topBlock.receiver();
this.homeContext.variables.parentFrame =
this.homeContext.receiver.variables;
this.context = new Context(
@ -651,7 +660,7 @@ Process.prototype.evaluateBlock = function (block, argCount) {
}
// first evaluate all inputs, then apply the primitive
var rcvr = this.context.receiver || this.topBlock.receiver(),
var rcvr = this.context.receiver || this.receiver,
inputs = this.context.inputs;
if (argCount > inputs.length) {
@ -963,7 +972,7 @@ Process.prototype.reify = function (topBlock, parameterNames, isCustomBlock) {
context.inputs = parameterNames.asArray();
context.receiver
= this.context ? this.context.receiver : topBlock.receiver();
= this.context ? this.context.receiver : this.receiver;
context.origin = context.receiver; // for serialization
return context;
@ -1137,6 +1146,9 @@ Process.prototype.initializeFor = function (context, args) {
value,
exit;
// remember the receiver
this.context = context.receiver;
// assign parameters if any were passed
if (parms.length > 0) {
@ -1557,7 +1569,7 @@ Process.prototype.doDeleteAttr = function (attrName) {
if (!isNil(name)) {
rcvr.inheritAttribute(name);
}
return; // +++ error: cannot delete attribute...
return; // error: cannot delete attribute...
}
}
if (name instanceof Array) {
@ -2063,7 +2075,7 @@ Process.prototype.doThinkFor = function (data, secs) {
Process.prototype.blockReceiver = function () {
return this.context ? this.context.receiver || this.homeContext.receiver
: this.homeContext.receiver;
: this.homeContext.receiver || this.receiver;
};
// Process sound primitives (interpolated)
@ -2180,7 +2192,6 @@ Process.prototype.doBroadcast = function (message) {
trg,
rcvrs,
myself = this,
hats = [],
procs = [];
if (message instanceof List && (message.length() === 2)) {
@ -2211,40 +2222,19 @@ Process.prototype.doBroadcast = function (message) {
stage.lastMessage = message; // the actual data structure
rcvrs.forEach(function (morph) {
if (isSnapObject(morph)) {
hats = hats.concat(morph.allHatBlocksFor(msg));
morph.allHatBlocksFor(msg).forEach(function (block) {
procs.push(stage.threads.startProcess(
block,
morph,
stage.isThreadSafe
));
});
}
});
hats.forEach(function (block) {
procs.push(stage.threads.startProcess(block, stage.isThreadSafe));
});
}
return procs;
};
// old purely global broadcast code, commented out and retained in case
// we need to revert
/*
Process.prototype.doBroadcast = function (message) {
var stage = this.homeContext.receiver.parentThatIsA(StageMorph),
hats = [],
procs = [];
if (message !== '') {
stage.lastMessage = message;
stage.children.concat(stage).forEach(function (morph) {
if (isSnapObject(morph)) {
hats = hats.concat(morph.allHatBlocksFor(message));
}
});
hats.forEach(function (block) {
procs.push(stage.threads.startProcess(block, stage.isThreadSafe));
});
}
return procs;
};
*/
Process.prototype.doBroadcastAndWait = function (message) {
if (!this.context.activeSends) {
this.context.activeSends = this.doBroadcast(message);