mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-06-28 23:30:22 -04:00
Prevent desync of currentCostume
This commit is contained in:
parent
113a4b2c8a
commit
cbc370ec58
3 changed files with 127 additions and 11 deletions
|
@ -505,18 +505,28 @@ class RenderedTarget extends Target {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletedCostume = this.sprite.deleteCostumeAt(index);
|
const deletedCostume = this.sprite.deleteCostumeAt(index, true); // Update current costume
|
||||||
|
|
||||||
if (index === this.currentCostume && index === originalCostumeCount - 1) {
|
this.runtime.requestTargetsUpdate(this);
|
||||||
|
return deletedCostume;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shift current costume index if needed and re-render this target.
|
||||||
|
* This needs to be called on all clones to prevent desync crash. (#2661)
|
||||||
|
* @param {number} index Index of deleted costume
|
||||||
|
* @param {boolean} isLastCostume Whether the deleted costume was the last costume or not
|
||||||
|
*/
|
||||||
|
shiftCurrentCostume (index, isLastCostume) {
|
||||||
|
if (index === this.currentCostume && isLastCostume) {
|
||||||
this.setCostume(index - 1);
|
this.setCostume(index - 1);
|
||||||
} else if (index < this.currentCostume) {
|
} else if (index < this.currentCostume) {
|
||||||
this.setCostume(this.currentCostume - 1);
|
this.setCostume(this.currentCostume - 1);
|
||||||
} else {
|
} else {
|
||||||
|
// Index is still the same; however, the costume itself may have changed.
|
||||||
|
// This causes the renderer to re-render.
|
||||||
this.setCostume(this.currentCostume);
|
this.setCostume(this.currentCostume);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.runtime.requestTargetsUpdate(this);
|
|
||||||
return deletedCostume;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -630,14 +640,15 @@ class RenderedTarget extends Target {
|
||||||
|
|
||||||
if (newIndex === costumeIndex) return false;
|
if (newIndex === costumeIndex) return false;
|
||||||
|
|
||||||
const currentCostume = this.getCurrentCostume();
|
this.sprite.saveCurrentCostumeName();
|
||||||
const costume = this.sprite.costumes[costumeIndex];
|
const costume = this.sprite.costumes[costumeIndex];
|
||||||
|
|
||||||
// Use the sprite method for deleting costumes because setCostume is handled manually
|
// Use the sprite method for deleting costumes because setCostume is handled manually
|
||||||
this.sprite.deleteCostumeAt(costumeIndex);
|
this.sprite.deleteCostumeAt(costumeIndex);
|
||||||
|
|
||||||
this.addCostume(costume, newIndex);
|
this.addCostume(costume, newIndex);
|
||||||
this.currentCostume = this.getCostumeIndexByName(currentCostume.name);
|
// This sets currentCostume
|
||||||
|
this.sprite.restoreCurrentCostume();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,12 +92,43 @@ class Sprite {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a costume by index.
|
* Delete a costume by index, and optionally update current costume for all clones.
|
||||||
* @param {number} index Costume index to be deleted
|
* @param {number} index Costume index to be deleted
|
||||||
|
* @param {boolean} [optUpdateCurrentCostume] Whether or not to update current costume for all clones
|
||||||
* @return {?object} The deleted costume
|
* @return {?object} The deleted costume
|
||||||
*/
|
*/
|
||||||
deleteCostumeAt (index) {
|
deleteCostumeAt (index, optUpdateCurrentCostume) {
|
||||||
return this.costumes.splice(index, 1)[0];
|
const isLastCostume = index === this.costumes.length - 1;
|
||||||
|
const deletedCostume = this.costumes.splice(index, 1)[0];
|
||||||
|
if (optUpdateCurrentCostume) {
|
||||||
|
for (const target of this.clones) {
|
||||||
|
target.shiftCurrentCostume(index, isLastCostume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return deletedCostume;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save current costume temporarliy during reordering, for all clones.
|
||||||
|
*/
|
||||||
|
saveCurrentCostumeName () {
|
||||||
|
for (const target of this.clones) {
|
||||||
|
target._currentCostumeName = target.getCurrentCostume().name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore current costume from saved name.
|
||||||
|
*/
|
||||||
|
restoreCurrentCostume () {
|
||||||
|
for (const target of this.clones) {
|
||||||
|
if (typeof target._currentCostumeName === 'undefined') continue;
|
||||||
|
const costumeIndex = this.costumes.findIndex(c => c.name === target._currentCostumeName);
|
||||||
|
delete target._currentCostumeName;
|
||||||
|
if (costumeIndex !== target.currentCostume) {
|
||||||
|
target.setCostume(costumeIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -119,6 +119,7 @@ test('deleteCostume', t => {
|
||||||
const a = new RenderedTarget(s, r);
|
const a = new RenderedTarget(s, r);
|
||||||
const renderer = new FakeRenderer();
|
const renderer = new FakeRenderer();
|
||||||
a.renderer = renderer;
|
a.renderer = renderer;
|
||||||
|
s.clones = [a];
|
||||||
|
|
||||||
// x* Costume 1 * Costume 2
|
// x* Costume 1 * Costume 2
|
||||||
// Costume 2 => Costume 3
|
// Costume 2 => Costume 3
|
||||||
|
@ -211,6 +212,70 @@ test('deleteCostume', t => {
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('deleteCostume shifts current costume (1/2)', t => {
|
||||||
|
const o1 = {id: 1};
|
||||||
|
const o2 = {id: 2};
|
||||||
|
const o3 = {id: 3};
|
||||||
|
|
||||||
|
const r = new Runtime();
|
||||||
|
const s = new Sprite(null, r);
|
||||||
|
s.costumes = [o1, o2, o3];
|
||||||
|
const renderer = new FakeRenderer();
|
||||||
|
|
||||||
|
const c1 = new RenderedTarget(s, r);
|
||||||
|
c1.renderer = renderer;
|
||||||
|
c1.currentCostume = 0;
|
||||||
|
|
||||||
|
const c2 = new RenderedTarget(s, r);
|
||||||
|
c2.renderer = renderer;
|
||||||
|
c2.currentCostume = 1;
|
||||||
|
|
||||||
|
const c3 = new RenderedTarget(s, r);
|
||||||
|
c3.renderer = renderer;
|
||||||
|
c3.currentCostume = 2;
|
||||||
|
|
||||||
|
s.clones = [c1, c2, c3];
|
||||||
|
|
||||||
|
c1.deleteCostume(0);
|
||||||
|
t.equals(c1.currentCostume, 0);
|
||||||
|
t.equals(c2.currentCostume, 0);
|
||||||
|
t.equals(c3.currentCostume, 1);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('deleteCostume shifts current costume (2/2)', t => {
|
||||||
|
const o1 = {id: 1};
|
||||||
|
const o2 = {id: 2};
|
||||||
|
const o3 = {id: 3};
|
||||||
|
|
||||||
|
const r = new Runtime();
|
||||||
|
const s = new Sprite(null, r);
|
||||||
|
s.costumes = [o1, o2, o3];
|
||||||
|
const renderer = new FakeRenderer();
|
||||||
|
|
||||||
|
const c1 = new RenderedTarget(s, r);
|
||||||
|
c1.renderer = renderer;
|
||||||
|
c1.currentCostume = 0;
|
||||||
|
|
||||||
|
const c2 = new RenderedTarget(s, r);
|
||||||
|
c2.renderer = renderer;
|
||||||
|
c2.currentCostume = 1;
|
||||||
|
|
||||||
|
const c3 = new RenderedTarget(s, r);
|
||||||
|
c3.renderer = renderer;
|
||||||
|
c3.currentCostume = 2;
|
||||||
|
|
||||||
|
s.clones = [c1, c2, c3];
|
||||||
|
|
||||||
|
c1.deleteCostume(2);
|
||||||
|
t.equals(c1.currentCostume, 0);
|
||||||
|
t.equals(c2.currentCostume, 1);
|
||||||
|
t.equals(c3.currentCostume, 1);
|
||||||
|
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
test('deleteSound', t => {
|
test('deleteSound', t => {
|
||||||
const o1 = {id: 1};
|
const o1 = {id: 1};
|
||||||
const o2 = {id: 2};
|
const o2 = {id: 2};
|
||||||
|
@ -474,12 +539,16 @@ test('#reorderCostume', t => {
|
||||||
const r = new Runtime();
|
const r = new Runtime();
|
||||||
const s = new Sprite(null, r);
|
const s = new Sprite(null, r);
|
||||||
s.costumes = [o1, o2, o3, o4, o5];
|
s.costumes = [o1, o2, o3, o4, o5];
|
||||||
const a = new RenderedTarget(s, r);
|
|
||||||
const renderer = new FakeRenderer();
|
const renderer = new FakeRenderer();
|
||||||
|
const a = new RenderedTarget(s, r);
|
||||||
a.renderer = renderer;
|
a.renderer = renderer;
|
||||||
|
const b = new RenderedTarget(s, r);
|
||||||
|
b.renderer = renderer;
|
||||||
|
s.clones = [a, b];
|
||||||
|
|
||||||
const resetCostumes = () => {
|
const resetCostumes = () => {
|
||||||
a.setCostume(0);
|
a.setCostume(0);
|
||||||
|
b.setCostume(0);
|
||||||
s.costumes = [o1, o2, o3, o4, o5];
|
s.costumes = [o1, o2, o3, o4, o5];
|
||||||
};
|
};
|
||||||
const costumeIds = () => a.sprite.costumes.map(c => c.id);
|
const costumeIds = () => a.sprite.costumes.map(c => c.id);
|
||||||
|
@ -487,6 +556,7 @@ test('#reorderCostume', t => {
|
||||||
resetCostumes();
|
resetCostumes();
|
||||||
t.deepEquals(costumeIds(), [0, 1, 2, 3, 4]);
|
t.deepEquals(costumeIds(), [0, 1, 2, 3, 4]);
|
||||||
t.equals(a.currentCostume, 0);
|
t.equals(a.currentCostume, 0);
|
||||||
|
t.equals(b.currentCostume, 0);
|
||||||
|
|
||||||
// Returns false if the costumes are the same and no change occurred
|
// Returns false if the costumes are the same and no change occurred
|
||||||
t.equal(a.reorderCostume(3, 3), false);
|
t.equal(a.reorderCostume(3, 3), false);
|
||||||
|
@ -495,15 +565,19 @@ test('#reorderCostume', t => {
|
||||||
|
|
||||||
// Make sure reordering up and down works and current costume follows
|
// Make sure reordering up and down works and current costume follows
|
||||||
resetCostumes();
|
resetCostumes();
|
||||||
|
b.setCostume(4);
|
||||||
t.equal(a.reorderCostume(0, 3), true);
|
t.equal(a.reorderCostume(0, 3), true);
|
||||||
t.deepEquals(costumeIds(), [1, 2, 3, 0, 4]);
|
t.deepEquals(costumeIds(), [1, 2, 3, 0, 4]);
|
||||||
t.equals(a.currentCostume, 3); // Index of id=0
|
t.equals(a.currentCostume, 3); // Index of id=0
|
||||||
|
t.equals(b.currentCostume, 4); // Index of id=4
|
||||||
|
|
||||||
resetCostumes();
|
resetCostumes();
|
||||||
a.setCostume(1);
|
a.setCostume(1);
|
||||||
|
b.setCostume(3);
|
||||||
t.equal(a.reorderCostume(3, 1), true);
|
t.equal(a.reorderCostume(3, 1), true);
|
||||||
t.deepEquals(costumeIds(), [0, 3, 1, 2, 4]);
|
t.deepEquals(costumeIds(), [0, 3, 1, 2, 4]);
|
||||||
t.equals(a.currentCostume, 2); // Index of id=1
|
t.equals(a.currentCostume, 2); // Index of id=1
|
||||||
|
t.equals(b.currentCostume, 1); // Index of id=3
|
||||||
|
|
||||||
// Out of bounds indices get clamped
|
// Out of bounds indices get clamped
|
||||||
resetCostumes();
|
resetCostumes();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue