Skip to content

Commit 7133302

Browse files
committed
cloning speedup
significantly speed up sprite cloning through partial shallow-copying of scripts and costumes instead of deep-duplication
1 parent d7479f9 commit 7133302

File tree

7 files changed

+91
-45
lines changed

7 files changed

+91
-45
lines changed

blocks.js

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,11 @@ radians, useBlurredShadows, SpeechBubbleMorph, modules, StageMorph,
145145
fontHeight, TableFrameMorph, SpriteMorph, Context, ListWatcherMorph,
146146
CellMorph, DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph,
147147
Costume, IDE_Morph, BlockDialogMorph, BlockEditorMorph, localize, isNil,
148-
isSnapObject*/
148+
isSnapObject, copy*/
149149

150150
// Global stuff ////////////////////////////////////////////////////////
151151

152-
modules.blocks = '2016-May-02';
152+
modules.blocks = '2016-May-04';
153153

154154
var SyntaxElementMorph;
155155
var BlockMorph;
@@ -3251,7 +3251,14 @@ BlockMorph.prototype.setCategory = function (aString) {
32513251

32523252
// BlockMorph copying
32533253

3254-
BlockMorph.prototype.fullCopy = function () {
3254+
BlockMorph.prototype.fullCopy = function (forClone) {
3255+
if (forClone) {
3256+
if (this.hasBlockVars()) {
3257+
forClone = false;
3258+
} else {
3259+
return copy(this);
3260+
}
3261+
}
32553262
var ans = BlockMorph.uber.fullCopy.call(this);
32563263
ans.removeHighlight();
32573264
ans.isDraggable = true;
@@ -3261,22 +3268,15 @@ BlockMorph.prototype.fullCopy = function () {
32613268
ans.allChildren().filter(function (block) {
32623269
if (block instanceof SyntaxElementMorph) {
32633270
block.cachedInputs = null;
3264-
// if (block instanceof InputSlotMorph) {
3265-
// block.contents().clearSelection();
3266-
// } else
32673271
if (block.definition) {
32683272
block.initializeVariables();
32693273
}
3270-
// } else if (block instanceof CursorMorph) {
3271-
// block.destroy();
32723274
}
32733275
return !isNil(block.comment);
32743276
}).forEach(function (block) {
32753277
var cmnt = block.comment.fullCopy();
32763278
block.comment = cmnt;
32773279
cmnt.block = block;
3278-
//block.comment = null;
3279-
32803280
});
32813281
ans.cachedInputs = null;
32823282
return ans;
@@ -3286,6 +3286,12 @@ BlockMorph.prototype.reactToTemplateCopy = function () {
32863286
this.forceNormalColoring();
32873287
};
32883288

3289+
BlockMorph.prototype.hasBlockVars = function () {
3290+
return this.anyChild(function (any) {
3291+
return any.definition && any.definition.variableNames.length;
3292+
});
3293+
};
3294+
32893295
// BlockMorph events
32903296

32913297
BlockMorph.prototype.mouseClickLeft = function () {
@@ -5177,7 +5183,7 @@ ScriptsMorph.prototype.init = function (owner) {
51775183

51785184
// ScriptsMorph deep copying:
51795185

5180-
ScriptsMorph.prototype.fullCopy = function () {
5186+
ScriptsMorph.prototype.fullCopy = function (forClone) {
51815187
var cpy = new ScriptsMorph(),
51825188
pos = this.position(),
51835189
child;
@@ -5186,17 +5192,21 @@ ScriptsMorph.prototype.fullCopy = function () {
51865192
}
51875193
this.children.forEach(function (morph) {
51885194
if (!morph.block) { // omit anchored comments
5189-
child = morph.fullCopy();
5190-
child.setPosition(morph.position().subtract(pos));
5195+
child = morph.fullCopy(forClone);
51915196
cpy.add(child);
5192-
if (child instanceof BlockMorph) {
5193-
child.allComments().forEach(function (comment) {
5194-
comment.align(child);
5195-
});
5197+
if (!forClone) {
5198+
child.setPosition(morph.position().subtract(pos));
5199+
if (child instanceof BlockMorph) {
5200+
child.allComments().forEach(function (comment) {
5201+
comment.align(child);
5202+
});
5203+
}
51965204
}
51975205
}
51985206
});
5199-
cpy.adjustBounds();
5207+
if (!forClone) {
5208+
cpy.adjustBounds();
5209+
}
52005210
return cpy;
52015211
};
52025212

gui.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ isSnapObject*/
7070

7171
// Global stuff ////////////////////////////////////////////////////////
7272

73-
modules.gui = '2016-May-02';
73+
modules.gui = '2016-May-04';
7474

7575
// Declarations
7676

@@ -2835,7 +2835,7 @@ IDE_Morph.prototype.aboutSnap = function () {
28352835
module, btn1, btn2, btn3, btn4, licenseBtn, translatorsBtn,
28362836
world = this.world();
28372837

2838-
aboutTxt = 'Snap! 4.0.7\nBuild Your Own Blocks\n\n'
2838+
aboutTxt = 'Snap! 4.0.7.1\nBuild Your Own Blocks\n\n'
28392839
+ 'Copyright \u24B8 2016 Jens M\u00F6nig and '
28402840
+ 'Brian Harvey\n'
28412841

history.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2908,10 +2908,15 @@ http://snap.berkeley.edu/run#cloud:Username=jens&ProjectName=PathFollower
29082908
http://snap.berkeley.edu/run#present:Username=jens&ProjectName=cartwheel
29092909
http://snap.berkeley.edu/run#cloud:Username=jens&ProjectName=rotation
29102910

2911+
* new Indonesian translation. Yay!! Thank you, Alexander Liu!!
29112912
* Translation updates: Slovenian, Portuguese, Chinese
29122913
* minor bug fixes
29132914

29142915
== v4.0.7 ==== - first class sprites
29152916

2917+
160504
2918+
------
2919+
* Morphic, Objects, Blocks, Threads, GUI: Partially shallow-copy clones for speed
2920+
* new Estonian translation! Yay!! Thanks, Hasso Tepper!
29162921

2917-
2922+
== v4.0.7.1 ==== - cloning speed-up

locale.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242

4343
/*global modules, contains*/
4444

45-
modules.locale = '2016-May-02';
45+
modules.locale = '2016-May-04';
4646

4747
// Global stuff
4848

morphic.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@
337337
(c) an application
338338
-------------------
339339
Of course, most of the time you don't want to just plain use the
340-
standard Morhic World "as is" out of the box, but write your own
340+
standard Morphic World "as is" out of the box, but write your own
341341
application (something like Scratch!) in it. For such an
342342
application you'll create your own morph prototypes, perhaps
343343
assemble your own "window frame" and bring it all to life in a
@@ -1054,10 +1054,9 @@
10541054

10551055
// Global settings /////////////////////////////////////////////////////
10561056

1057-
/*global window, HTMLCanvasElement, getMinimumFontHeight, FileReader, Audio,
1058-
FileList, getBlurredShadowSupport*/
1057+
/*global window, HTMLCanvasElement, FileReader, Audio, FileList*/
10591058

1060-
var morphicVersion = '2016-February-24';
1059+
var morphicVersion = '2016-May-04';
10611060
var modules = {}; // keep track of additional loaded modules
10621061
var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug
10631062

@@ -1938,7 +1937,7 @@ Rectangle.prototype.round = function () {
19381937
Rectangle.prototype.spread = function () {
19391938
// round me by applying floor() to my origin and ceil() to my corner
19401939
// expand by 1 to be on the safe side, this eliminates rounding
1941-
// artefacts caused by Safari's auto-scaling on retina displays
1940+
// artifacts caused by Safari's auto-scaling on retina displays
19421941
return this.origin.floor().corner(this.corner.ceil()).expandBy(1);
19431942
};
19441943

@@ -2090,6 +2089,20 @@ Node.prototype.forAllChildren = function (aFunction) {
20902089
aFunction.call(null, this);
20912090
};
20922091

2092+
Node.prototype.anyChild = function (aPredicate) {
2093+
// includes myself
2094+
var i;
2095+
if (aPredicate.call(null, this)) {
2096+
return true;
2097+
}
2098+
for (i = 0; i < this.children.length; i += 1) {
2099+
if (this.children[i].anyChild(aPredicate)) {
2100+
return true;
2101+
}
2102+
}
2103+
return false;
2104+
};
2105+
20932106
Node.prototype.allLeafs = function () {
20942107
var result = [];
20952108
this.allChildren().forEach(function (element) {

objects.js

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ SpeechBubbleMorph, RingMorph, isNil, FileReader, TableDialogMorph,
8282
BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, localize,
8383
TableMorph, TableFrameMorph*/
8484

85-
modules.objects = '2016-May-02';
85+
modules.objects = '2016-May-04';
8686

8787
var SpriteMorph;
8888
var StageMorph;
@@ -1364,7 +1364,7 @@ SpriteMorph.prototype.init = function (globals) {
13641364

13651365
// SpriteMorph duplicating (fullCopy)
13661366

1367-
SpriteMorph.prototype.fullCopy = function () {
1367+
SpriteMorph.prototype.fullCopy = function (forClone) {
13681368
var c = SpriteMorph.uber.fullCopy.call(this),
13691369
myself = this,
13701370
arr = [],
@@ -1374,20 +1374,22 @@ SpriteMorph.prototype.fullCopy = function () {
13741374
c.color = this.color.copy();
13751375
c.blocksCache = {};
13761376
c.paletteCache = {};
1377-
c.scripts = this.scripts.fullCopy();
1377+
c.scripts = this.scripts.fullCopy(forClone);
13781378
c.scripts.owner = c;
13791379
c.variables = this.variables.copy();
13801380
c.variables.owner = c;
13811381
c.customBlocks = [];
1382-
this.customBlocks.forEach(function (def) {
1383-
cb = def.copyAndBindTo(c);
1384-
c.customBlocks.push(cb);
1385-
c.allBlockInstances(def).forEach(function (block) {
1386-
block.definition = cb;
1382+
if (!forClone) {
1383+
this.customBlocks.forEach(function (def) {
1384+
cb = def.copyAndBindTo(c);
1385+
c.customBlocks.push(cb);
1386+
c.allBlockInstances(def).forEach(function (block) {
1387+
block.definition = cb;
1388+
});
13871389
});
1388-
});
1390+
}
13891391
this.costumes.asArray().forEach(function (costume) {
1390-
var cst = costume.copy();
1392+
var cst = forClone ? costume : costume.copy();
13911393
arr.push(cst);
13921394
if (costume === myself.costume) {
13931395
c.costume = cst;
@@ -1404,12 +1406,11 @@ SpriteMorph.prototype.fullCopy = function () {
14041406
c.anchor = null;
14051407
c.parts = [];
14061408
this.parts.forEach(function (part) {
1407-
var dp = part.fullCopy();
1409+
var dp = part.fullCopy(forClone);
14081410
dp.nestingScale = part.nestingScale;
14091411
dp.rotatesWithAnchor = part.rotatesWithAnchor;
14101412
c.attachPart(dp);
14111413
});
1412-
14131414
return c;
14141415
};
14151416

@@ -2834,12 +2835,30 @@ SpriteMorph.prototype.remove = function () {
28342835
}
28352836
};
28362837

2837-
// SpriteMorph cloning (experimental)
2838+
// SpriteMorph cloning
2839+
2840+
/*
2841+
clones are temporary, partially shallow copies of sprites that don't
2842+
appear as icons in the corral. Clones get deleted when the red stop button
2843+
is pressed. Shallow-copying clones' scripts and costumes makes spawning
2844+
very fast, so they can be used for particle system simulations.
2845+
This speed-up, however, comes at the cost of some detrimental side
2846+
effects: Changes to a costume or a script of the original sprite are
2847+
in some cases shared with all of its clones, however such shared changes
2848+
are hard to predict for users and not actively propagated, so they don't
2849+
offer any reliable feature, and will not be supported as such.
2850+
Changes to the original sprite's scripts affect all of its clones, unless
2851+
the script contains any custom block whose definition contains one or more
2852+
block variables (in which case the script does get deep-copied).
2853+
The original sprite's scripting area, costumes wardrobe or sounds jukebox
2854+
are also not shared. therefore adding or deleting a script, sound or
2855+
costume in the original sprite has no effect on any of its clones.
2856+
*/
28382857

28392858
SpriteMorph.prototype.createClone = function () {
28402859
var stage = this.parentThatIsA(StageMorph);
2841-
if (stage && stage.cloneCount <= 1000) {
2842-
this.fullCopy().clonify(stage);
2860+
if (stage && stage.cloneCount <= 2000) {
2861+
this.fullCopy(true).clonify(stage);
28432862
}
28442863
};
28452864

@@ -6636,7 +6655,6 @@ Costume.prototype.copy = function () {
66366655
var canvas = newCanvas(this.extent()),
66376656
cpy,
66386657
ctx;
6639-
66406658
ctx = canvas.getContext('2d');
66416659
ctx.drawImage(this.contents, 0, 0);
66426660
cpy = new Costume(canvas, this.name ? copy(this.name) : null);

threads.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ StageMorph, SpriteMorph, StagePrompterMorph, Note, modules, isString, copy,
6161
isNil, WatcherMorph, List, ListWatcherMorph, alert, console, TableMorph,
6262
TableFrameMorph, isSnapObject*/
6363

64-
modules.threads = '2016-May-02';
64+
modules.threads = '2016-May-04';
6565

6666
var ThreadManager;
6767
var Process;
@@ -354,7 +354,7 @@ ThreadManager.prototype.doWhen = function (block, stopIt) {
354354
if (invoke(
355355
pred,
356356
null,
357-
null,
357+
block.receiver(), // needed for shallow copied clones - was null
358358
50,
359359
'the predicate takes\ntoo long for a\ncustom hat block',
360360
true // suppress errors => handle them right here instead

0 commit comments

Comments
 (0)