diff --git a/blocks.js b/blocks.js index 8a482fdf..e75c357f 100644 --- a/blocks.js +++ b/blocks.js @@ -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, diff --git a/byob.js b/byob.js index f43de0ba..75b23169 100644 --- a/byob.js +++ b/byob.js @@ -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; diff --git a/gui.js b/gui.js index 28c17025..42e3f28a 100644 --- a/gui.js +++ b/gui.js @@ -74,7 +74,7 @@ isRetinaSupported, SliderMorph, Animation*/ // Global stuff //////////////////////////////////////////////////////// -modules.gui = '2017-May-12'; +modules.gui = '2017-May-30'; // Declarations diff --git a/history.txt b/history.txt index 51bfedb9..269c7122 100755 --- a/history.txt +++ b/history.txt @@ -3428,6 +3428,10 @@ Fixes: * added inheritance support for the wardrobe (‘costumes’) * added inheritance support for ‘costume #’ +170530 +------ +* let clones share the orginal’s 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 * sprites’s rotation centers can be adjusted onstage +* clones share their original sprite’s scripts, not a shallow-copy of them Fixes: * changed keyboard shortcut indicator for “find blocks” to “^” diff --git a/objects.js b/objects.js index 8e90b0cf..3ebbe2bf 100644 --- a/objects.js +++ b/objects.js @@ -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 diff --git a/threads.js b/threads.js index d66c53df..1cfd6a1f 100644 --- a/threads.js +++ b/threads.js @@ -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);