@scratch/scratch-vm 13.7.3 → 13.7.4-svg
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/node/scratch-vm.js +1 -1
- package/dist/web/scratch-vm.js +1 -1
- package/package.json +3 -3
- package/src/engine/target.js +86 -147
- package/src/virtual-machine.js +1 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scratch/scratch-vm",
|
|
3
|
-
"version": "13.7.
|
|
3
|
+
"version": "13.7.4-svg",
|
|
4
4
|
"description": "Virtual Machine for Scratch 3.0",
|
|
5
5
|
"keywords": [],
|
|
6
6
|
"homepage": "https://github.com/scratchfoundation/scratch-vm#readme",
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
"allow-incomplete-coverage": true
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@scratch/scratch-render": "13.7.
|
|
64
|
-
"@scratch/scratch-svg-renderer": "13.7.
|
|
63
|
+
"@scratch/scratch-render": "13.7.4-svg",
|
|
64
|
+
"@scratch/scratch-svg-renderer": "13.7.4-svg",
|
|
65
65
|
"@vernier/godirect": "1.8.3",
|
|
66
66
|
"arraybuffer-loader": "1.0.8",
|
|
67
67
|
"atob": "2.1.2",
|
package/src/engine/target.js
CHANGED
|
@@ -654,38 +654,52 @@ class Target extends EventEmitter {
|
|
|
654
654
|
}
|
|
655
655
|
|
|
656
656
|
/**
|
|
657
|
-
*
|
|
658
|
-
*
|
|
659
|
-
*
|
|
660
|
-
*
|
|
657
|
+
* Fixes up variable references in this target avoiding conflicts with
|
|
658
|
+
* pre-existing variables in the same scope.
|
|
659
|
+
* This is used when uploading this target as a new sprite into an existing
|
|
660
|
+
* project, where the new sprite may contain references
|
|
661
|
+
* to variable names that already exist as global variables in the project
|
|
662
|
+
* (and thus are in scope for variable references in the given sprite).
|
|
661
663
|
*
|
|
662
|
-
*
|
|
663
|
-
*
|
|
664
|
-
*
|
|
665
|
-
*
|
|
666
|
-
* variable with the same name and type. If one exists, the field id is
|
|
667
|
-
* remapped to that existing global. Otherwise, a new global is created on
|
|
668
|
-
* the stage with a non-conflicting name and the field name is updated.
|
|
664
|
+
* If this target has a block that references an existing global variable and that
|
|
665
|
+
* variable *does not* exist in this target (e.g. it was a global variable in the
|
|
666
|
+
* project the sprite was originally exported from), merge the variables. This entails
|
|
667
|
+
* fixing the variable references in this sprite to reference the id of the pre-existing global variable.
|
|
669
668
|
*
|
|
670
|
-
*
|
|
671
|
-
*
|
|
672
|
-
*
|
|
669
|
+
* If this target has a block that references an existing global variable and that
|
|
670
|
+
* variable does exist in the target itself (e.g. it's a local variable in the sprite being uploaded),
|
|
671
|
+
* then the local variable is renamed to distinguish itself from the pre-existing variable.
|
|
672
|
+
* All blocks that reference the local variable will be updated to use the new name.
|
|
673
673
|
*/
|
|
674
|
-
|
|
675
|
-
|
|
674
|
+
// TODO (#1360) This function is too long, add some helpers for the different chunks and cases...
|
|
675
|
+
fixUpVariableReferences () {
|
|
676
|
+
if (!this.runtime) return; // There's no runtime context to conflict with
|
|
677
|
+
if (this.isStage) return; // Stage can't have variable conflicts with itself (and also can't be uploaded)
|
|
676
678
|
const stage = this.runtime.getTargetForStage();
|
|
677
679
|
if (!stage || !stage.variables) return;
|
|
678
680
|
|
|
679
|
-
const
|
|
681
|
+
const renameConflictingLocalVar = (id, name, type) => {
|
|
682
|
+
const conflict = stage.lookupVariableByNameAndType(name, type);
|
|
683
|
+
if (conflict) {
|
|
684
|
+
const newName = StringUtil.unusedName(
|
|
685
|
+
`${this.getName()}: ${name}`,
|
|
686
|
+
this.getAllVariableNamesInScopeByType(type));
|
|
687
|
+
this.renameVariable(id, newName);
|
|
688
|
+
return newName;
|
|
689
|
+
}
|
|
690
|
+
return null;
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
const allReferences = this.blocks.getAllVariableAndListReferences();
|
|
694
|
+
const unreferencedLocalVarIds = [];
|
|
695
|
+
if (Object.keys(this.variables).length > 0) {
|
|
696
|
+
for (const localVarId in this.variables) {
|
|
697
|
+
if (!Object.prototype.hasOwnProperty.call(this.variables, localVarId)) continue;
|
|
698
|
+
if (!allReferences[localVarId]) unreferencedLocalVarIds.push(localVarId);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
680
701
|
const conflictIdsToReplace = Object.create(null);
|
|
681
702
|
const conflictNamesToReplace = Object.create(null);
|
|
682
|
-
// When a dangling reference triggers creation of a new stage variable, remember
|
|
683
|
-
// the original (pre-bump) name so subsequent dangling references with the same
|
|
684
|
-
// original name and type coalesce to the same stage variable instead of creating
|
|
685
|
-
// a second one. Scratchers who pasted scripts referencing what they called
|
|
686
|
-
// "score" twice almost certainly meant one variable, not two.
|
|
687
|
-
const createdForOriginalName = Object.create(null);
|
|
688
|
-
const originalNameKey = (name, type) => `${type}\u0000${name}`;
|
|
689
703
|
|
|
690
704
|
// Cache the list of all variable names by type so that we don't need to
|
|
691
705
|
// re-calculate this in every iteration of the following loop.
|
|
@@ -698,92 +712,77 @@ class Target extends EventEmitter {
|
|
|
698
712
|
};
|
|
699
713
|
|
|
700
714
|
for (const varId in allReferences) {
|
|
701
|
-
|
|
702
|
-
if (existing) {
|
|
703
|
-
// The id resolves to a real variable. Normalize displayed names so
|
|
704
|
-
// every block field shows the variable's current name. A previous
|
|
705
|
-
// target's pass on this load may have created the stage variable with
|
|
706
|
-
// a bumped name; refs on this target that still show the original
|
|
707
|
-
// name need to be brought into agreement. Scan all refs (not just
|
|
708
|
-
// the first) so an inconsistent set of refs to the same id heals
|
|
709
|
-
// even when the first ref happens to already match.
|
|
710
|
-
if (!conflictNamesToReplace[varId]) {
|
|
711
|
-
const staleRef = allReferences[varId].find(
|
|
712
|
-
ref => ref.referencingField.value !== existing.name
|
|
713
|
-
);
|
|
714
|
-
if (staleRef) {
|
|
715
|
-
conflictNamesToReplace[varId] = existing.name;
|
|
716
|
-
log.warn(
|
|
717
|
-
`Reconciled stale displayed name on '${this.getName()}': updated to ` +
|
|
718
|
-
`'${existing.name}' for id '${varId}' ` +
|
|
719
|
-
`(was '${staleRef.referencingField.value}').`
|
|
720
|
-
);
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
continue;
|
|
724
|
-
}
|
|
725
|
-
// The referenced id is not defined anywhere. Treat this as a reference
|
|
726
|
-
// to a global from a different project (or from a backpack paste / sprite
|
|
727
|
-
// import that lost its definition). Look for a same-name same-type global
|
|
728
|
-
// on the stage; if found, queue an id remap, otherwise create a fresh one.
|
|
715
|
+
// We don't care about which var ref we get, they should all have the same var info
|
|
729
716
|
const varRef = allReferences[varId][0];
|
|
730
717
|
const varName = varRef.referencingField.value;
|
|
731
718
|
const varType = varRef.type;
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
);
|
|
719
|
+
if (this.lookupVariableById(varId)) {
|
|
720
|
+
// Found a variable with the id in either the target or the stage,
|
|
721
|
+
// figure out which one.
|
|
722
|
+
if (Object.prototype.hasOwnProperty.call(this.variables, varId)) {
|
|
723
|
+
// If the target has the variable, then check whether the stage
|
|
724
|
+
// has one with the same name and type. If it does, then rename
|
|
725
|
+
// this target specific variable so that there is a distinction.
|
|
726
|
+
const newVarName = renameConflictingLocalVar(varId, varName, varType);
|
|
727
|
+
|
|
728
|
+
if (newVarName) {
|
|
729
|
+
// We are not calling this.blocks.updateBlocksAfterVarRename
|
|
730
|
+
// here because it will search through all the blocks. We already
|
|
731
|
+
// have access to all the references for this var id.
|
|
732
|
+
allReferences[varId].map(ref => {
|
|
733
|
+
ref.referencingField.value = newVarName;
|
|
734
|
+
return ref;
|
|
735
|
+
});
|
|
736
|
+
}
|
|
740
737
|
}
|
|
741
738
|
} else {
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
739
|
+
// We didn't find the referenced variable id anywhere,
|
|
740
|
+
// Treat it as a reference to a global variable (from the original
|
|
741
|
+
// project this sprite was exported from).
|
|
742
|
+
// Check for whether a global variable of the same name and type exists,
|
|
743
|
+
// and if so, track it to merge with the existing global in a second pass of the blocks.
|
|
744
|
+
const existingVar = stage.lookupVariableByNameAndType(varName, varType);
|
|
745
|
+
if (existingVar) {
|
|
749
746
|
if (!conflictIdsToReplace[varId]) {
|
|
750
|
-
conflictIdsToReplace[varId] =
|
|
751
|
-
conflictNamesToReplace[varId] = earlierCreated.freshName;
|
|
752
|
-
log.warn(
|
|
753
|
-
`Reconciled dangling reference on '${this.getName()}': coalesced id '${varId}' ` +
|
|
754
|
-
`(name '${varName}', type '${varType}') with earlier-created stage variable ` +
|
|
755
|
-
`'${earlierCreated.id}' (name '${earlierCreated.freshName}').`
|
|
756
|
-
);
|
|
747
|
+
conflictIdsToReplace[varId] = existingVar.id;
|
|
757
748
|
}
|
|
758
749
|
} else {
|
|
750
|
+
// A global variable with the same name did not already exist,
|
|
751
|
+
// create a new one such that it does not conflict with any
|
|
752
|
+
// names of local variables of the same type.
|
|
759
753
|
const allNames = allVarNames(varType);
|
|
760
754
|
const freshName = StringUtil.unusedName(varName, allNames);
|
|
761
755
|
stage.createVariable(varId, freshName, varType);
|
|
762
|
-
// Track the new name so unusedName accounts for it on subsequent calls,
|
|
763
|
-
// and remember which stage variable served this original name so
|
|
764
|
-
// future same-name dangling references coalesce instead of duplicating.
|
|
765
|
-
allNames.push(freshName);
|
|
766
|
-
createdForOriginalName[coalesceKey] = {id: varId, freshName};
|
|
767
756
|
if (!conflictNamesToReplace[varId]) {
|
|
768
757
|
conflictNamesToReplace[varId] = freshName;
|
|
769
|
-
log.warn(
|
|
770
|
-
`Reconciled dangling reference on '${this.getName()}': created stage variable ` +
|
|
771
|
-
`'${varId}' (name '${freshName}', type '${varType}').`
|
|
772
|
-
);
|
|
773
758
|
}
|
|
774
759
|
}
|
|
775
760
|
}
|
|
776
761
|
}
|
|
777
|
-
|
|
778
|
-
//
|
|
762
|
+
// Rename any local variables that were missed above because they aren't
|
|
763
|
+
// referenced by any blocks
|
|
764
|
+
for (const id in unreferencedLocalVarIds) {
|
|
765
|
+
const varId = unreferencedLocalVarIds[id];
|
|
766
|
+
const name = this.variables[varId].name;
|
|
767
|
+
const type = this.variables[varId].type;
|
|
768
|
+
renameConflictingLocalVar(varId, name, type);
|
|
769
|
+
}
|
|
770
|
+
// Handle global var conflicts with existing global vars (e.g. a sprite is uploaded, and has
|
|
771
|
+
// blocks referencing some variable that the sprite does not own, and this
|
|
772
|
+
// variable conflicts with a global var)
|
|
773
|
+
// In this case, we want to merge the new variable referenes with the
|
|
774
|
+
// existing global variable
|
|
779
775
|
for (const conflictId in conflictIdsToReplace) {
|
|
780
776
|
const existingId = conflictIdsToReplace[conflictId];
|
|
781
777
|
const referencesToUpdate = allReferences[conflictId];
|
|
782
778
|
this.mergeVariables(conflictId, existingId, referencesToUpdate);
|
|
783
779
|
}
|
|
784
780
|
|
|
785
|
-
//
|
|
786
|
-
//
|
|
781
|
+
// Handle global var conflicts existing local vars (e.g a sprite is uploaded,
|
|
782
|
+
// and has blocks referencing some variable that the sprite does not own, and this
|
|
783
|
+
// variable conflcits with another sprite's local var).
|
|
784
|
+
// In this case, we want to go through the variable references and update
|
|
785
|
+
// the name of the variable in that reference.
|
|
787
786
|
for (const conflictId in conflictNamesToReplace) {
|
|
788
787
|
const newName = conflictNamesToReplace[conflictId];
|
|
789
788
|
const referencesToUpdate = allReferences[conflictId];
|
|
@@ -794,66 +793,6 @@ class Target extends EventEmitter {
|
|
|
794
793
|
}
|
|
795
794
|
}
|
|
796
795
|
|
|
797
|
-
/**
|
|
798
|
-
* Reconciles missing definitions (via `reconcileVariableReferences`) and then
|
|
799
|
-
* renames sprite-local variables that name-collide with stage globals so they
|
|
800
|
-
* remain distinguishable after import.
|
|
801
|
-
*
|
|
802
|
-
* Used when importing a sprite into an existing project and when pasting
|
|
803
|
-
* blocks from the backpack. Project load uses `reconcileVariableReferences`
|
|
804
|
-
* directly so that legitimately-existing local-vs-global name collisions in
|
|
805
|
-
* a saved project are not renamed.
|
|
806
|
-
*/
|
|
807
|
-
fixUpVariableReferences () {
|
|
808
|
-
if (!this.runtime) return;
|
|
809
|
-
const stage = this.runtime.getTargetForStage();
|
|
810
|
-
if (!stage || !stage.variables) return;
|
|
811
|
-
|
|
812
|
-
// First, ensure every referenced variable, list, or broadcast has a definition.
|
|
813
|
-
this.reconcileVariableReferences();
|
|
814
|
-
|
|
815
|
-
// The stage's variables are the global scope; there's no local-vs-global
|
|
816
|
-
// distinction to disambiguate.
|
|
817
|
-
if (this.isStage) return;
|
|
818
|
-
|
|
819
|
-
const renameConflictingLocalVar = (id, name, type) => {
|
|
820
|
-
const conflict = stage.lookupVariableByNameAndType(name, type);
|
|
821
|
-
if (conflict) {
|
|
822
|
-
const newName = StringUtil.unusedName(
|
|
823
|
-
`${this.getName()}: ${name}`,
|
|
824
|
-
this.getAllVariableNamesInScopeByType(type));
|
|
825
|
-
this.renameVariable(id, newName);
|
|
826
|
-
return newName;
|
|
827
|
-
}
|
|
828
|
-
return null;
|
|
829
|
-
};
|
|
830
|
-
|
|
831
|
-
// Walk the references again and rename any sprite-local variables whose
|
|
832
|
-
// name and type collide with a stage global. References on this target
|
|
833
|
-
// that point at the local are updated to use the new name.
|
|
834
|
-
const allReferences = this.blocks.getAllVariableAndListReferences(null, true);
|
|
835
|
-
for (const varId in allReferences) {
|
|
836
|
-
if (!Object.prototype.hasOwnProperty.call(this.variables, varId)) continue;
|
|
837
|
-
const varRef = allReferences[varId][0];
|
|
838
|
-
const newVarName = renameConflictingLocalVar(varId, varRef.referencingField.value, varRef.type);
|
|
839
|
-
if (newVarName) {
|
|
840
|
-
allReferences[varId].map(ref => {
|
|
841
|
-
ref.referencingField.value = newVarName;
|
|
842
|
-
return ref;
|
|
843
|
-
});
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
// Rename any local variables that aren't referenced by any block but still
|
|
848
|
-
// collide with a stage global, so the sprite remains internally consistent.
|
|
849
|
-
for (const localVarId in this.variables) {
|
|
850
|
-
if (!Object.prototype.hasOwnProperty.call(this.variables, localVarId)) continue;
|
|
851
|
-
if (allReferences[localVarId]) continue;
|
|
852
|
-
const v = this.variables[localVarId];
|
|
853
|
-
renameConflictingLocalVar(localVarId, v.name, v.type);
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
|
|
857
796
|
}
|
|
858
797
|
|
|
859
798
|
module.exports = Target;
|
package/src/virtual-machine.js
CHANGED
|
@@ -560,13 +560,7 @@ class VirtualMachine extends EventEmitter {
|
|
|
560
560
|
this.editingTarget = targets[0];
|
|
561
561
|
}
|
|
562
562
|
|
|
563
|
-
if (wholeProject) {
|
|
564
|
-
// A loaded project may carry dangling variable, list, or broadcast
|
|
565
|
-
// references baked in by historical bugs. Reconcile each target so
|
|
566
|
-
// those references resolve cleanly without renaming any legitimate
|
|
567
|
-
// local-vs-global name collisions.
|
|
568
|
-
targets.forEach(target => target.reconcileVariableReferences());
|
|
569
|
-
} else {
|
|
563
|
+
if (!wholeProject) {
|
|
570
564
|
this.editingTarget.fixUpVariableReferences();
|
|
571
565
|
}
|
|
572
566
|
|
|
@@ -1286,12 +1280,6 @@ class VirtualMachine extends EventEmitter {
|
|
|
1286
1280
|
target.blocks.createBlock(block);
|
|
1287
1281
|
});
|
|
1288
1282
|
target.blocks.updateTargetSpecificBlocks(target.isStage);
|
|
1289
|
-
if (!optFromTargetId) {
|
|
1290
|
-
// No source target means the blocks come from outside the project (e.g. the
|
|
1291
|
-
// backpack). Reconcile any variable, list, or broadcast references against
|
|
1292
|
-
// what's defined in the project, creating missing definitions on the stage.
|
|
1293
|
-
target.fixUpVariableReferences();
|
|
1294
|
-
}
|
|
1295
1283
|
});
|
|
1296
1284
|
}
|
|
1297
1285
|
|