From 71333021d4bc60943d54e11b56fcc25f5c6c7ecb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jens=20M=C3=B6nig?= Date: Wed, 4 May 2016 16:37:46 +0200 Subject: [PATCH] cloning speedup significantly speed up sprite cloning through partial shallow-copying of scripts and costumes instead of deep-duplication --- blocks.js | 46 ++++++++++++++++++++++++++++------------------ gui.js | 4 ++-- history.txt | 7 ++++++- locale.js | 2 +- morphic.js | 23 ++++++++++++++++++----- objects.js | 50 ++++++++++++++++++++++++++++++++++---------------- threads.js | 4 ++-- 7 files changed, 91 insertions(+), 45 deletions(-) diff --git a/blocks.js b/blocks.js index ecebca3422..a3aa284897 100644 --- a/blocks.js +++ b/blocks.js @@ -145,11 +145,11 @@ radians, useBlurredShadows, SpeechBubbleMorph, modules, StageMorph, fontHeight, TableFrameMorph, SpriteMorph, Context, ListWatcherMorph, CellMorph, DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph, Costume, IDE_Morph, BlockDialogMorph, BlockEditorMorph, localize, isNil, -isSnapObject*/ +isSnapObject, copy*/ // Global stuff //////////////////////////////////////////////////////// -modules.blocks = '2016-May-02'; +modules.blocks = '2016-May-04'; var SyntaxElementMorph; var BlockMorph; @@ -3251,7 +3251,14 @@ BlockMorph.prototype.setCategory = function (aString) { // BlockMorph copying -BlockMorph.prototype.fullCopy = function () { +BlockMorph.prototype.fullCopy = function (forClone) { + if (forClone) { + if (this.hasBlockVars()) { + forClone = false; + } else { + return copy(this); + } + } var ans = BlockMorph.uber.fullCopy.call(this); ans.removeHighlight(); ans.isDraggable = true; @@ -3261,22 +3268,15 @@ BlockMorph.prototype.fullCopy = function () { ans.allChildren().filter(function (block) { if (block instanceof SyntaxElementMorph) { block.cachedInputs = null; -// if (block instanceof InputSlotMorph) { -// block.contents().clearSelection(); -// } else if (block.definition) { block.initializeVariables(); } -// } else if (block instanceof CursorMorph) { -// block.destroy(); } return !isNil(block.comment); }).forEach(function (block) { var cmnt = block.comment.fullCopy(); block.comment = cmnt; cmnt.block = block; - //block.comment = null; - }); ans.cachedInputs = null; return ans; @@ -3286,6 +3286,12 @@ BlockMorph.prototype.reactToTemplateCopy = function () { this.forceNormalColoring(); }; +BlockMorph.prototype.hasBlockVars = function () { + return this.anyChild(function (any) { + return any.definition && any.definition.variableNames.length; + }); +}; + // BlockMorph events BlockMorph.prototype.mouseClickLeft = function () { @@ -5177,7 +5183,7 @@ ScriptsMorph.prototype.init = function (owner) { // ScriptsMorph deep copying: -ScriptsMorph.prototype.fullCopy = function () { +ScriptsMorph.prototype.fullCopy = function (forClone) { var cpy = new ScriptsMorph(), pos = this.position(), child; @@ -5186,17 +5192,21 @@ ScriptsMorph.prototype.fullCopy = function () { } this.children.forEach(function (morph) { if (!morph.block) { // omit anchored comments - child = morph.fullCopy(); - child.setPosition(morph.position().subtract(pos)); + child = morph.fullCopy(forClone); cpy.add(child); - if (child instanceof BlockMorph) { - child.allComments().forEach(function (comment) { - comment.align(child); - }); + if (!forClone) { + child.setPosition(morph.position().subtract(pos)); + if (child instanceof BlockMorph) { + child.allComments().forEach(function (comment) { + comment.align(child); + }); + } } } }); - cpy.adjustBounds(); + if (!forClone) { + cpy.adjustBounds(); + } return cpy; }; diff --git a/gui.js b/gui.js index 3b50847120..49450387b7 100644 --- a/gui.js +++ b/gui.js @@ -70,7 +70,7 @@ isSnapObject*/ // Global stuff //////////////////////////////////////////////////////// -modules.gui = '2016-May-02'; +modules.gui = '2016-May-04'; // Declarations @@ -2835,7 +2835,7 @@ IDE_Morph.prototype.aboutSnap = function () { module, btn1, btn2, btn3, btn4, licenseBtn, translatorsBtn, world = this.world(); - aboutTxt = 'Snap! 4.0.7\nBuild Your Own Blocks\n\n' + aboutTxt = 'Snap! 4.0.7.1\nBuild Your Own Blocks\n\n' + 'Copyright \u24B8 2016 Jens M\u00F6nig and ' + 'Brian Harvey\n' + 'jens@moenig.org, bh@cs.berkeley.edu\n\n' diff --git a/history.txt b/history.txt index 4fad694413..fe6a778e1c 100755 --- a/history.txt +++ b/history.txt @@ -2908,10 +2908,15 @@ http://snap.berkeley.edu/run#cloud:Username=jens&ProjectName=PathFollower http://snap.berkeley.edu/run#present:Username=jens&ProjectName=cartwheel http://snap.berkeley.edu/run#cloud:Username=jens&ProjectName=rotation +* new Indonesian translation. Yay!! Thank you, Alexander Liu!! * Translation updates: Slovenian, Portuguese, Chinese * minor bug fixes == v4.0.7 ==== - first class sprites +160504 +------ +* Morphic, Objects, Blocks, Threads, GUI: Partially shallow-copy clones for speed +* new Estonian translation! Yay!! Thanks, Hasso Tepper! - +== v4.0.7.1 ==== - cloning speed-up diff --git a/locale.js b/locale.js index 75233992b1..51d85b58a0 100644 --- a/locale.js +++ b/locale.js @@ -42,7 +42,7 @@ /*global modules, contains*/ -modules.locale = '2016-May-02'; +modules.locale = '2016-May-04'; // Global stuff diff --git a/morphic.js b/morphic.js index b3567300be..5502fa51cb 100644 --- a/morphic.js +++ b/morphic.js @@ -337,7 +337,7 @@ (c) an application ------------------- Of course, most of the time you don't want to just plain use the - standard Morhic World "as is" out of the box, but write your own + standard Morphic World "as is" out of the box, but write your own application (something like Scratch!) in it. For such an application you'll create your own morph prototypes, perhaps assemble your own "window frame" and bring it all to life in a @@ -1054,10 +1054,9 @@ // Global settings ///////////////////////////////////////////////////// -/*global window, HTMLCanvasElement, getMinimumFontHeight, FileReader, Audio, -FileList, getBlurredShadowSupport*/ +/*global window, HTMLCanvasElement, FileReader, Audio, FileList*/ -var morphicVersion = '2016-February-24'; +var morphicVersion = '2016-May-04'; var modules = {}; // keep track of additional loaded modules var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug @@ -1938,7 +1937,7 @@ Rectangle.prototype.round = function () { Rectangle.prototype.spread = function () { // round me by applying floor() to my origin and ceil() to my corner // expand by 1 to be on the safe side, this eliminates rounding - // artefacts caused by Safari's auto-scaling on retina displays + // artifacts caused by Safari's auto-scaling on retina displays return this.origin.floor().corner(this.corner.ceil()).expandBy(1); }; @@ -2090,6 +2089,20 @@ Node.prototype.forAllChildren = function (aFunction) { aFunction.call(null, this); }; +Node.prototype.anyChild = function (aPredicate) { + // includes myself + var i; + if (aPredicate.call(null, this)) { + return true; + } + for (i = 0; i < this.children.length; i += 1) { + if (this.children[i].anyChild(aPredicate)) { + return true; + } + } + return false; +}; + Node.prototype.allLeafs = function () { var result = []; this.allChildren().forEach(function (element) { diff --git a/objects.js b/objects.js index 25348feba7..b4538d0ab8 100644 --- a/objects.js +++ b/objects.js @@ -82,7 +82,7 @@ SpeechBubbleMorph, RingMorph, isNil, FileReader, TableDialogMorph, BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, localize, TableMorph, TableFrameMorph*/ -modules.objects = '2016-May-02'; +modules.objects = '2016-May-04'; var SpriteMorph; var StageMorph; @@ -1364,7 +1364,7 @@ SpriteMorph.prototype.init = function (globals) { // SpriteMorph duplicating (fullCopy) -SpriteMorph.prototype.fullCopy = function () { +SpriteMorph.prototype.fullCopy = function (forClone) { var c = SpriteMorph.uber.fullCopy.call(this), myself = this, arr = [], @@ -1374,20 +1374,22 @@ SpriteMorph.prototype.fullCopy = function () { c.color = this.color.copy(); c.blocksCache = {}; c.paletteCache = {}; - c.scripts = this.scripts.fullCopy(); + c.scripts = this.scripts.fullCopy(forClone); c.scripts.owner = c; c.variables = this.variables.copy(); c.variables.owner = c; c.customBlocks = []; - this.customBlocks.forEach(function (def) { - cb = def.copyAndBindTo(c); - c.customBlocks.push(cb); - c.allBlockInstances(def).forEach(function (block) { - block.definition = cb; + if (!forClone) { + this.customBlocks.forEach(function (def) { + cb = def.copyAndBindTo(c); + c.customBlocks.push(cb); + c.allBlockInstances(def).forEach(function (block) { + block.definition = cb; + }); }); - }); + } this.costumes.asArray().forEach(function (costume) { - var cst = costume.copy(); + var cst = forClone ? costume : costume.copy(); arr.push(cst); if (costume === myself.costume) { c.costume = cst; @@ -1404,12 +1406,11 @@ SpriteMorph.prototype.fullCopy = function () { c.anchor = null; c.parts = []; this.parts.forEach(function (part) { - var dp = part.fullCopy(); + var dp = part.fullCopy(forClone); dp.nestingScale = part.nestingScale; dp.rotatesWithAnchor = part.rotatesWithAnchor; c.attachPart(dp); }); - return c; }; @@ -2834,12 +2835,30 @@ SpriteMorph.prototype.remove = function () { } }; -// SpriteMorph cloning (experimental) +// SpriteMorph cloning + +/* + clones are temporary, partially shallow copies of sprites that don't + appear as icons in the corral. Clones get deleted when the red stop button + is pressed. Shallow-copying clones' scripts and costumes makes spawning + very fast, so they can be used for particle system simulations. + This speed-up, however, comes at the cost of some detrimental side + effects: Changes to a costume or a script of the original sprite are + in some cases shared with all of its clones, however such shared changes + are hard to predict for users and not actively propagated, so they don't + offer any reliable feature, and will not be supported as such. + Changes to the original sprite's scripts affect all of its clones, unless + the script contains any custom block whose definition contains one or more + block variables (in which case the script does get deep-copied). + The original sprite's scripting area, costumes wardrobe or sounds jukebox + are also not shared. therefore adding or deleting a script, sound or + costume in the original sprite has no effect on any of its clones. +*/ SpriteMorph.prototype.createClone = function () { var stage = this.parentThatIsA(StageMorph); - if (stage && stage.cloneCount <= 1000) { - this.fullCopy().clonify(stage); + if (stage && stage.cloneCount <= 2000) { + this.fullCopy(true).clonify(stage); } }; @@ -6636,7 +6655,6 @@ Costume.prototype.copy = function () { var canvas = newCanvas(this.extent()), cpy, ctx; - ctx = canvas.getContext('2d'); ctx.drawImage(this.contents, 0, 0); cpy = new Costume(canvas, this.name ? copy(this.name) : null); diff --git a/threads.js b/threads.js index ae31ecf028..c532e439c5 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, isSnapObject*/ -modules.threads = '2016-May-02'; +modules.threads = '2016-May-04'; var ThreadManager; var Process; @@ -354,7 +354,7 @@ ThreadManager.prototype.doWhen = function (block, stopIt) { if (invoke( pred, null, - null, + block.receiver(), // needed for shallow copied clones - was null 50, 'the predicate takes\ntoo long for a\ncustom hat block', true // suppress errors => handle them right here instead