How TurboWarp runs Scratch projects 10-100x faster | TurboWarp Documentation (original) (raw)
TurboWarp uses a compiler while Scratch uses an interpreter. This allows TurboWarp to run somewhere between 10-100x faster depending on the project, but it makes live script editing impracticable.
Scratch's interpreter walks an abstract syntax tree at runtime. Internally that looks like this:
{
"va[U{Cbi_NZpSOSx_kVA": {
"opcode": "event_whenflagclicked",
"inputs": {},
"fields": {},
"next": "tzXnZ{8G!xK|t^WAWF{m",
"topLevel": true
},
"tzXnZ{8G!xK|t^WAWF{m": {
"opcode": "control_forever",
"inputs": {
"SUBSTACK": {
"name": "SUBSTACK",
"block": "$xf$bq|xl(}RhT-K,taS"
}
},
"fields": {},
"next": null,
"topLevel": false
},
"$xf$bq|xl(}RhT-K,taS": {
"opcode": "motion_movesteps",
"inputs": {
"STEPS": {
"name": "STEPS",
"block": "cw__.I:g}Y~`:5KmO00q"
}
},
"fields": {},
"next": null,
"topLevel": false
},
"cw__.I:g}Y~`:5KmO00q": {
"opcode": "data_variable",
"inputs": {},
"fields": {
"VARIABLE": {
"name": "VARIABLE",
"id": "`jEk@4|i[#Fk?(8x)AV.-my variable"
}
},
"next": null,
"topLevel": false
}
}
The interpreter overhead is added on top of the overhead of JavaScript itself. As this code involves many dynamic types, it can be hard for the JavaScript JIT to optimize it.
TurboWarp's compiler removes all of that overhead by converting scripts directly to JavaScript functions. For example, the above script becomes:
Of course, this is a very simple script where the interpreter overhead is negligible, which is the case for most projects. It's only when you execute thousands of blocks per frame that the interpreter's overhead becomes significant.
Here's a more complex example: a naive sorting algorithm (bubble sort).
const length = stage.variables["O;aH~(njYNn}Bl@}!%pS-length-"];
const list = stage.variables["O;aH~(njYNn}Bl@}!%pS-list-list"];
const newLength = stage.variables["O;aH~(njYNn}Bl@}!%pS-new-"];
const i = stage.variables["O;aH~(njYNn}Bl@}!%pS-i-"];
const temp = stage.variables["O;aH~(njYNn}Bl@}!%pS-tmp-"];
return function fun1_sort () {
length.value = list.value.length;
// repeat until length = 0
while (!compareEqual(length.value, 0)) {
newLength.value = 0;
i.value = 1;
// repeat length - 1 times
for (var counter = ((+length.value || 0) - 1) || 0; counter >= 0.5; counter--) {
// change i by 1
i.value = ((+i.value || 0) + 1);
// if item i - 1 of list is greater than item i of list
if (
compareGreaterThan(
list.value[((((i.value || 0) - 1) || 0) | 0) - 1] ?? "",
list.value[((i.value || 0) | 0) - 1] ?? ""
)
) {
// swap item i and i - 1 of list
temp.value = listGet(list.value, i.value);
listReplace(
list,
i.value,
list.value[((((+i.value || 0) - 1) || 0) | 0) - 1] ?? ""
);
listReplace(
list,
(+i.value || 0) - 1,
temp.value
);
newLength.value = i.value;
}
}
length.value = newLength.value;
}
};
If you start a script using the compiler, you won't be able to move, remove, or add blocks and have the changes be reflected in real time as they would be in Scratch. The script has to be restarted for changes to apply. We believe there are some ways we could make this work, but they will hurt performance or add significant complexity. It's something we want to implement eventually, but not yet.