@scratch/scratch-vm 11.0.0-beta.1
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/.jsdoc.json +20 -0
- package/.nvmrc +1 -0
- package/CHANGELOG.md +7004 -0
- package/LICENSE +12 -0
- package/README.md +120 -0
- package/TRADEMARK +1 -0
- package/commitlint.config.js +4 -0
- package/docs/extensions.md +527 -0
- package/package.json +103 -0
- package/release.config.js +13 -0
- package/src/blocks/scratch3_control.js +206 -0
- package/src/blocks/scratch3_core_example.js +69 -0
- package/src/blocks/scratch3_data.js +257 -0
- package/src/blocks/scratch3_event.js +137 -0
- package/src/blocks/scratch3_looks.js +612 -0
- package/src/blocks/scratch3_motion.js +288 -0
- package/src/blocks/scratch3_operators.js +154 -0
- package/src/blocks/scratch3_procedures.js +79 -0
- package/src/blocks/scratch3_sensing.js +336 -0
- package/src/blocks/scratch3_sound.js +350 -0
- package/src/dispatch/central-dispatch.js +143 -0
- package/src/dispatch/shared-dispatch.js +235 -0
- package/src/dispatch/worker-dispatch.js +110 -0
- package/src/engine/adapter.js +176 -0
- package/src/engine/block-utility.js +242 -0
- package/src/engine/blocks-execute-cache.js +19 -0
- package/src/engine/blocks-runtime-cache.js +78 -0
- package/src/engine/blocks.js +1299 -0
- package/src/engine/comment.js +56 -0
- package/src/engine/execute.js +580 -0
- package/src/engine/monitor-record.js +23 -0
- package/src/engine/mutation-adapter.js +48 -0
- package/src/engine/profiler.js +390 -0
- package/src/engine/runtime.js +2686 -0
- package/src/engine/scratch-blocks-constants.js +27 -0
- package/src/engine/sequencer.js +361 -0
- package/src/engine/stage-layering.js +29 -0
- package/src/engine/target.js +799 -0
- package/src/engine/thread.js +404 -0
- package/src/engine/variable.js +70 -0
- package/src/extension-support/argument-type.js +47 -0
- package/src/extension-support/block-type.js +50 -0
- package/src/extension-support/define-messages.js +18 -0
- package/src/extension-support/extension-manager.js +440 -0
- package/src/extension-support/extension-metadata.js +64 -0
- package/src/extension-support/extension-worker.js +59 -0
- package/src/extension-support/reporter-scope.js +18 -0
- package/src/extension-support/target-type.js +17 -0
- package/src/extensions/scratch3_boost/index.js +2113 -0
- package/src/extensions/scratch3_ev3/index.js +1355 -0
- package/src/extensions/scratch3_gdx_for/index.js +981 -0
- package/src/extensions/scratch3_gdx_for/scratch-link-device-adapter.js +44 -0
- package/src/extensions/scratch3_makeymakey/index.js +396 -0
- package/src/extensions/scratch3_microbit/index.js +984 -0
- package/src/extensions/scratch3_music/assets/drums/1-snare.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/10-wood-block.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/11-cowbell.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/12-triangle.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/13-bongo.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/14-conga.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/15-cabasa.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/16-guiro.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/17-vibraslap.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/18-cuica.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/2-bass-drum.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/3-side-stick.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/4-crash-cymbal.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/5-open-hi-hat.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/6-closed-hi-hat.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/7-tambourine.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/8-hand-clap.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/9-claves.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/108.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/24.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/84.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/96.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/10-clarinet/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/10-clarinet/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/84.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/12-flute/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/12-flute/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/17-music-box/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/18-steel-drum/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/19-marimba/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/2-electric-piano/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/20-synth-lead/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/21-synth-pad/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/3-organ/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/4-guitar/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/5-electric-guitar/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/6-bass/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/6-bass/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/7-pizzicato/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/index.js +1333 -0
- package/src/extensions/scratch3_music/manifest.js +63 -0
- package/src/extensions/scratch3_pen/index.js +770 -0
- package/src/extensions/scratch3_speech2text/index.js +700 -0
- package/src/extensions/scratch3_text2speech/index.js +766 -0
- package/src/extensions/scratch3_translate/index.js +286 -0
- package/src/extensions/scratch3_video_sensing/debug.js +13 -0
- package/src/extensions/scratch3_video_sensing/index.js +594 -0
- package/src/extensions/scratch3_video_sensing/library.js +384 -0
- package/src/extensions/scratch3_video_sensing/math.js +76 -0
- package/src/extensions/scratch3_video_sensing/view.js +509 -0
- package/src/extensions/scratch3_wedo2/index.js +1616 -0
- package/src/import/load-costume.js +416 -0
- package/src/import/load-sound.js +116 -0
- package/src/index.js +10 -0
- package/src/io/ble.js +256 -0
- package/src/io/bt.js +202 -0
- package/src/io/clock.js +39 -0
- package/src/io/cloud.js +169 -0
- package/src/io/keyboard.js +146 -0
- package/src/io/mouse.js +145 -0
- package/src/io/mouseWheel.js +28 -0
- package/src/io/userData.js +24 -0
- package/src/io/video.js +210 -0
- package/src/playground/benchmark.css +83 -0
- package/src/playground/benchmark.js +753 -0
- package/src/playground/index.html +111 -0
- package/src/playground/suite.css +78 -0
- package/src/playground/suite.html +25 -0
- package/src/playground/suite.js +544 -0
- package/src/playground/video-sensing.html +18 -0
- package/src/playground/video-sensing.js +133 -0
- package/src/serialization/deserialize-assets.js +178 -0
- package/src/serialization/sb2.js +1295 -0
- package/src/serialization/sb2_specmap.js +1818 -0
- package/src/serialization/sb3.js +1320 -0
- package/src/serialization/serialize-assets.js +60 -0
- package/src/sprites/rendered-target.js +1115 -0
- package/src/sprites/sprite.js +177 -0
- package/src/util/base64-util.js +48 -0
- package/src/util/cast.js +218 -0
- package/src/util/clone.js +17 -0
- package/src/util/color.js +204 -0
- package/src/util/fetch-with-timeout.js +53 -0
- package/src/util/get-monitor-id.js +33 -0
- package/src/util/jsonrpc.js +114 -0
- package/src/util/log.js +4 -0
- package/src/util/math-util.js +122 -0
- package/src/util/maybe-format-message.js +18 -0
- package/src/util/new-block-ids.js +33 -0
- package/src/util/rateLimiter.js +73 -0
- package/src/util/scratch-link-websocket.js +133 -0
- package/src/util/string-util.js +94 -0
- package/src/util/task-queue.js +203 -0
- package/src/util/timer.js +103 -0
- package/src/util/uid.js +29 -0
- package/src/util/variable-util.js +48 -0
- package/src/util/xml-escape.js +33 -0
- package/src/virtual-machine.js +1586 -0
- package/webpack.config.js +134 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
const Timer = require('../util/timer');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This class uses the token bucket algorithm to control a queue of tasks.
|
|
5
|
+
*/
|
|
6
|
+
class TaskQueue {
|
|
7
|
+
/**
|
|
8
|
+
* Creates an instance of TaskQueue.
|
|
9
|
+
* To allow bursts, set `maxTokens` to several times the average task cost.
|
|
10
|
+
* To prevent bursts, set `maxTokens` to the cost of the largest tasks.
|
|
11
|
+
* Note that tasks with a cost greater than `maxTokens` will be rejected.
|
|
12
|
+
*
|
|
13
|
+
* @param {number} maxTokens - the maximum number of tokens in the bucket (burst size).
|
|
14
|
+
* @param {number} refillRate - the number of tokens to be added per second (sustain rate).
|
|
15
|
+
* @param {object} options - optional settings for the new task queue instance.
|
|
16
|
+
* @property {number} startingTokens - the number of tokens the bucket starts with (default: `maxTokens`).
|
|
17
|
+
* @property {number} maxTotalCost - reject a task if total queue cost would pass this limit (default: no limit).
|
|
18
|
+
* @memberof TaskQueue
|
|
19
|
+
*/
|
|
20
|
+
constructor (maxTokens, refillRate, options = {}) {
|
|
21
|
+
this._maxTokens = maxTokens;
|
|
22
|
+
this._refillRate = refillRate;
|
|
23
|
+
this._pendingTaskRecords = [];
|
|
24
|
+
this._tokenCount = Object.prototype.hasOwnProperty.call(options, 'startingTokens') ?
|
|
25
|
+
options.startingTokens : maxTokens;
|
|
26
|
+
this._maxTotalCost = Object.prototype.hasOwnProperty.call(options, 'maxTotalCost') ?
|
|
27
|
+
options.maxTotalCost : Infinity;
|
|
28
|
+
this._timer = new Timer();
|
|
29
|
+
this._timer.start();
|
|
30
|
+
this._timeout = null;
|
|
31
|
+
this._lastUpdateTime = this._timer.timeElapsed();
|
|
32
|
+
|
|
33
|
+
this._runTasks = this._runTasks.bind(this);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Get the number of queued tasks which have not yet started.
|
|
38
|
+
*
|
|
39
|
+
* @readonly
|
|
40
|
+
* @memberof TaskQueue
|
|
41
|
+
*/
|
|
42
|
+
get length () {
|
|
43
|
+
return this._pendingTaskRecords.length;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Wait until the token bucket is full enough, then run the provided task.
|
|
48
|
+
*
|
|
49
|
+
* @param {Function} task - the task to run.
|
|
50
|
+
* @param {number} [cost=1] - the number of tokens this task consumes from the bucket.
|
|
51
|
+
* @returns {Promise} - a promise for the task's return value.
|
|
52
|
+
* @memberof TaskQueue
|
|
53
|
+
*/
|
|
54
|
+
do (task, cost = 1) {
|
|
55
|
+
if (this._maxTotalCost < Infinity) {
|
|
56
|
+
const currentTotalCost = this._pendingTaskRecords.reduce((t, r) => t + r.cost, 0);
|
|
57
|
+
if (currentTotalCost + cost > this._maxTotalCost) {
|
|
58
|
+
return Promise.reject(new Error('Maximum total cost exceeded'));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const newRecord = {
|
|
62
|
+
cost
|
|
63
|
+
};
|
|
64
|
+
newRecord.promise = new Promise((resolve, reject) => {
|
|
65
|
+
newRecord.cancel = () => {
|
|
66
|
+
reject(new Error('Task canceled'));
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// The caller, `_runTasks()`, is responsible for cost-checking and spending tokens.
|
|
70
|
+
newRecord.wrappedTask = () => {
|
|
71
|
+
try {
|
|
72
|
+
resolve(task());
|
|
73
|
+
} catch (e) {
|
|
74
|
+
reject(e);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
this._pendingTaskRecords.push(newRecord);
|
|
79
|
+
|
|
80
|
+
// If the queue has been idle we need to prime the pump
|
|
81
|
+
if (this._pendingTaskRecords.length === 1) {
|
|
82
|
+
this._runTasks();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return newRecord.promise;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Cancel one pending task, rejecting its promise.
|
|
90
|
+
*
|
|
91
|
+
* @param {Promise} taskPromise - the promise returned by `do()`.
|
|
92
|
+
* @returns {boolean} - true if the task was found, or false otherwise.
|
|
93
|
+
* @memberof TaskQueue
|
|
94
|
+
*/
|
|
95
|
+
cancel (taskPromise) {
|
|
96
|
+
const taskIndex = this._pendingTaskRecords.findIndex(r => r.promise === taskPromise);
|
|
97
|
+
if (taskIndex !== -1) {
|
|
98
|
+
const [taskRecord] = this._pendingTaskRecords.splice(taskIndex, 1);
|
|
99
|
+
taskRecord.cancel();
|
|
100
|
+
if (taskIndex === 0 && this._pendingTaskRecords.length > 0) {
|
|
101
|
+
this._runTasks();
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Cancel all pending tasks, rejecting all their promises.
|
|
110
|
+
*
|
|
111
|
+
* @memberof TaskQueue
|
|
112
|
+
*/
|
|
113
|
+
cancelAll () {
|
|
114
|
+
if (this._timeout !== null) {
|
|
115
|
+
this._timer.clearTimeout(this._timeout);
|
|
116
|
+
this._timeout = null;
|
|
117
|
+
}
|
|
118
|
+
const oldTasks = this._pendingTaskRecords;
|
|
119
|
+
this._pendingTaskRecords = [];
|
|
120
|
+
oldTasks.forEach(r => r.cancel());
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Shorthand for calling _refill() then _spend(cost).
|
|
125
|
+
*
|
|
126
|
+
* @see {@link TaskQueue#_refill}
|
|
127
|
+
* @see {@link TaskQueue#_spend}
|
|
128
|
+
* @param {number} cost - the number of tokens to try to spend.
|
|
129
|
+
* @returns {boolean} true if we had enough tokens; false otherwise.
|
|
130
|
+
* @memberof TaskQueue
|
|
131
|
+
*/
|
|
132
|
+
_refillAndSpend (cost) {
|
|
133
|
+
this._refill();
|
|
134
|
+
return this._spend(cost);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Refill the token bucket based on the amount of time since the last refill.
|
|
139
|
+
*
|
|
140
|
+
* @memberof TaskQueue
|
|
141
|
+
*/
|
|
142
|
+
_refill () {
|
|
143
|
+
const now = this._timer.timeElapsed();
|
|
144
|
+
const timeSinceRefill = now - this._lastUpdateTime;
|
|
145
|
+
if (timeSinceRefill <= 0) return;
|
|
146
|
+
|
|
147
|
+
this._lastUpdateTime = now;
|
|
148
|
+
this._tokenCount += timeSinceRefill * this._refillRate / 1000;
|
|
149
|
+
this._tokenCount = Math.min(this._tokenCount, this._maxTokens);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* If we can "afford" the given cost, subtract that many tokens and return true.
|
|
154
|
+
* Otherwise, return false.
|
|
155
|
+
*
|
|
156
|
+
* @param {number} cost - the number of tokens to try to spend.
|
|
157
|
+
* @returns {boolean} true if we had enough tokens; false otherwise.
|
|
158
|
+
* @memberof TaskQueue
|
|
159
|
+
*/
|
|
160
|
+
_spend (cost) {
|
|
161
|
+
if (cost <= this._tokenCount) {
|
|
162
|
+
this._tokenCount -= cost;
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Loop until the task queue is empty, running each task and spending tokens to do so.
|
|
170
|
+
* Any time the bucket can't afford the next task, delay asynchronously until it can.
|
|
171
|
+
*
|
|
172
|
+
* @memberof TaskQueue
|
|
173
|
+
*/
|
|
174
|
+
_runTasks () {
|
|
175
|
+
if (this._timeout) {
|
|
176
|
+
this._timer.clearTimeout(this._timeout);
|
|
177
|
+
this._timeout = null;
|
|
178
|
+
}
|
|
179
|
+
for (;;) {
|
|
180
|
+
const nextRecord = this._pendingTaskRecords.shift();
|
|
181
|
+
if (!nextRecord) {
|
|
182
|
+
// We ran out of work. Go idle until someone adds another task to the queue.
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (nextRecord.cost > this._maxTokens) {
|
|
186
|
+
throw new Error(`Task cost ${nextRecord.cost} is greater than bucket limit ${this._maxTokens}`);
|
|
187
|
+
}
|
|
188
|
+
// Refill before each task in case the time it took for the last task to run was enough to afford the next.
|
|
189
|
+
if (this._refillAndSpend(nextRecord.cost)) {
|
|
190
|
+
nextRecord.wrappedTask();
|
|
191
|
+
} else {
|
|
192
|
+
// We can't currently afford this task. Put it back and wait until we can and try again.
|
|
193
|
+
this._pendingTaskRecords.unshift(nextRecord);
|
|
194
|
+
const tokensNeeded = Math.max(nextRecord.cost - this._tokenCount, 0);
|
|
195
|
+
const estimatedWait = Math.ceil(1000 * tokensNeeded / this._refillRate);
|
|
196
|
+
this._timeout = this._timer.setTimeout(this._runTasks, estimatedWait);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
module.exports = TaskQueue;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview
|
|
3
|
+
* A utility for accurately measuring time.
|
|
4
|
+
* To use:
|
|
5
|
+
* ---
|
|
6
|
+
* var timer = new Timer();
|
|
7
|
+
* timer.start();
|
|
8
|
+
* ... pass some time ...
|
|
9
|
+
* var timeDifference = timer.timeElapsed();
|
|
10
|
+
* ---
|
|
11
|
+
* Or, you can use the `time` function
|
|
12
|
+
* to do some measurement yourself.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
class Timer {
|
|
16
|
+
constructor (nowObj = Timer.nowObj) {
|
|
17
|
+
/**
|
|
18
|
+
* Used to store the start time of a timer action.
|
|
19
|
+
* Updated when calling `timer.start`.
|
|
20
|
+
*/
|
|
21
|
+
this.startTime = 0;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Used to pass custom logic for determining the value for "now",
|
|
25
|
+
* which is sometimes useful for compatibility with Scratch 2
|
|
26
|
+
*/
|
|
27
|
+
this.nowObj = nowObj;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Disable use of self.performance for now as it results in lower performance
|
|
32
|
+
* However, instancing it like below (caching the self.performance to a local variable) negates most of the issues.
|
|
33
|
+
* @type {boolean}
|
|
34
|
+
*/
|
|
35
|
+
static get USE_PERFORMANCE () {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Legacy object to allow for us to call now to get the old style date time (for backwards compatibility)
|
|
41
|
+
* @deprecated This is only called via the nowObj.now() if no other means is possible...
|
|
42
|
+
*/
|
|
43
|
+
static get legacyDateCode () {
|
|
44
|
+
return {
|
|
45
|
+
now: function () {
|
|
46
|
+
return new Date().getTime();
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Use this object to route all time functions through single access points.
|
|
53
|
+
*/
|
|
54
|
+
static get nowObj () {
|
|
55
|
+
if (Timer.USE_PERFORMANCE && typeof self !== 'undefined' && self.performance && 'now' in self.performance) {
|
|
56
|
+
return self.performance;
|
|
57
|
+
} else if (Date.now) {
|
|
58
|
+
return Date;
|
|
59
|
+
}
|
|
60
|
+
return Timer.legacyDateCode;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Return the currently known absolute time, in ms precision.
|
|
65
|
+
* @returns {number} ms elapsed since 1 January 1970 00:00:00 UTC.
|
|
66
|
+
*/
|
|
67
|
+
time () {
|
|
68
|
+
return this.nowObj.now();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Start a timer for measuring elapsed time,
|
|
73
|
+
* at the most accurate precision possible.
|
|
74
|
+
*/
|
|
75
|
+
start () {
|
|
76
|
+
this.startTime = this.nowObj.now();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
timeElapsed () {
|
|
80
|
+
return this.nowObj.now() - this.startTime;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Call a handler function after a specified amount of time has elapsed.
|
|
85
|
+
* @param {function} handler - function to call after the timeout
|
|
86
|
+
* @param {number} timeout - number of milliseconds to delay before calling the handler
|
|
87
|
+
* @returns {number} - the ID of the new timeout
|
|
88
|
+
*/
|
|
89
|
+
setTimeout (handler, timeout) {
|
|
90
|
+
return global.setTimeout(handler, timeout);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Clear a timeout from the pending timeout pool.
|
|
95
|
+
* @param {number} timeoutId - the ID returned by `setTimeout()`
|
|
96
|
+
* @memberof Timer
|
|
97
|
+
*/
|
|
98
|
+
clearTimeout (timeoutId) {
|
|
99
|
+
global.clearTimeout(timeoutId);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = Timer;
|
package/src/util/uid.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview UID generator, from Blockly.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Legal characters for the unique ID.
|
|
7
|
+
* Should be all on a US keyboard. No XML special characters or control codes.
|
|
8
|
+
* Removed $ due to issue 251.
|
|
9
|
+
* @private
|
|
10
|
+
*/
|
|
11
|
+
const soup_ = '!#%()*+,-./:;=?@[]^_`{|}~' +
|
|
12
|
+
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generate a unique ID, from Blockly. This should be globally unique.
|
|
16
|
+
* 87 characters ^ 20 length > 128 bits (better than a UUID).
|
|
17
|
+
* @return {string} A globally unique ID string.
|
|
18
|
+
*/
|
|
19
|
+
const uid = function () {
|
|
20
|
+
const length = 20;
|
|
21
|
+
const soupLength = soup_.length;
|
|
22
|
+
const id = [];
|
|
23
|
+
for (let i = 0; i < length; i++) {
|
|
24
|
+
id[i] = soup_.charAt(Math.random() * soupLength);
|
|
25
|
+
}
|
|
26
|
+
return id.join('');
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
module.exports = uid;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
class VariableUtil {
|
|
2
|
+
static _mergeVarRefObjects (accum, obj2) {
|
|
3
|
+
for (const id in obj2) {
|
|
4
|
+
if (accum[id]) {
|
|
5
|
+
accum[id] = accum[id].concat(obj2[id]);
|
|
6
|
+
} else {
|
|
7
|
+
accum[id] = obj2[id];
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return accum;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get all variable/list references in the given list of targets
|
|
15
|
+
* in the project.
|
|
16
|
+
* @param {Array.<Target>} targets The list of targets to get the variable
|
|
17
|
+
* and list references from.
|
|
18
|
+
* @param {boolean} shouldIncludeBroadcast Whether to include broadcast message fields.
|
|
19
|
+
* @return {object} An object with variable ids as the keys and a list of block fields referencing
|
|
20
|
+
* the variable.
|
|
21
|
+
*/
|
|
22
|
+
static getAllVarRefsForTargets (targets, shouldIncludeBroadcast) {
|
|
23
|
+
return targets
|
|
24
|
+
.map(t => t.blocks.getAllVariableAndListReferences(null, shouldIncludeBroadcast))
|
|
25
|
+
.reduce(VariableUtil._mergeVarRefObjects, {});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Give all variable references provided a new id and possibly new name.
|
|
30
|
+
* @param {Array<object>} referencesToUpdate Context of the change, the object containing variable
|
|
31
|
+
* references to update.
|
|
32
|
+
* @param {string} newId ID of the variable that the old references should be replaced with
|
|
33
|
+
* @param {?string} optNewName New variable name to merge with. The old
|
|
34
|
+
* variable name in the references being updated should be replaced with this new name.
|
|
35
|
+
* If this parameter is not provided or is '', no name change occurs.
|
|
36
|
+
*/
|
|
37
|
+
static updateVariableIdentifiers (referencesToUpdate, newId, optNewName) {
|
|
38
|
+
referencesToUpdate.map(ref => {
|
|
39
|
+
ref.referencingField.id = newId;
|
|
40
|
+
if (optNewName) {
|
|
41
|
+
ref.referencingField.value = optNewName;
|
|
42
|
+
}
|
|
43
|
+
return ref;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = VariableUtil;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const log = require('./log');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Escape a string to be safe to use in XML content.
|
|
5
|
+
* CC-BY-SA: hgoebl
|
|
6
|
+
* https://stackoverflow.com/questions/7918868/
|
|
7
|
+
* how-to-escape-xml-entities-in-javascript
|
|
8
|
+
* @param {!string | !Array.<string>} unsafe Unsafe string.
|
|
9
|
+
* @return {string} XML-escaped string, for use within an XML tag.
|
|
10
|
+
*/
|
|
11
|
+
const xmlEscape = function (unsafe) {
|
|
12
|
+
if (typeof unsafe !== 'string') {
|
|
13
|
+
if (Array.isArray(unsafe)) {
|
|
14
|
+
// This happens when we have hacked blocks from 2.0
|
|
15
|
+
// See #1030
|
|
16
|
+
unsafe = String(unsafe);
|
|
17
|
+
} else {
|
|
18
|
+
log.error('Unexpected input recieved in replaceUnsafeChars');
|
|
19
|
+
return unsafe;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return unsafe.replace(/[<>&'"]/g, c => {
|
|
23
|
+
switch (c) {
|
|
24
|
+
case '<': return '<';
|
|
25
|
+
case '>': return '>';
|
|
26
|
+
case '&': return '&';
|
|
27
|
+
case '\'': return ''';
|
|
28
|
+
case '"': return '"';
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
module.exports = xmlEscape;
|