Spaces:
Sleeping
Sleeping
File size: 4,483 Bytes
7aec436 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
// TODO: Extract this and TurboWarp/scratch-vm's compression to a shared module
// We don't generate new IDs using numbers at this time because their enumeration
// order can affect script execution order as they always come first.
// https://tc39.es/ecma262/#sec-ordinaryownpropertykeys
const SOUP = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#%()*+,-./:;=?@[]^_`{|}~';
const generateId = i => {
let str = '';
while (i >= 0) {
str = SOUP[i % SOUP.length] + str;
i = Math.floor(i / SOUP.length) - 1;
}
return str;
};
class Pool {
constructor() {
this.generatedIds = new Map();
this.references = new Map();
this.skippedIds = new Set();
// IDs in Object.keys(vm.runtime.monitorBlocks._blocks) already have meaning, so make sure to skip those
// We don't bother listing many here because most would take more than ten million items to be used
this.skippedIds.add('of');
}
skip (id) {
this.skippedIds.add(id);
}
addReference(id) {
const currentCount = this.references.get(id) || 0;
this.references.set(id, currentCount + 1);
}
generateNewIds() {
const entries = Array.from(this.references.entries());
// The most used original IDs should get the shortest new IDs.
entries.sort((a, b) => b[1] - a[1]);
let i = 0;
for (const entry of entries) {
const oldId = entry[0];
let newId = generateId(i);
while (this.skippedIds.has(newId)) {
i++;
newId = generateId(i);
}
this.generatedIds.set(oldId, newId);
i++;
}
}
getNewId(originalId) {
if (this.generatedIds.has(originalId)) {
return this.generatedIds.get(originalId);
}
return originalId;
}
}
const optimizeSb3Json = (projectData) => {
// Note: we modify projectData in-place
// Scan global attributes of the project so we can generate optimal IDs later
const blockPool = new Pool();
for (const target of projectData.targets) {
for (const [blockId, block] of Object.entries(target.blocks)) {
blockPool.addReference(blockId);
if (Array.isArray(block)) {
continue;
}
if (block.parent) {
blockPool.addReference(block.parent);
}
if (block.next) {
blockPool.addReference(block.next);
}
for (const input of Object.values(block.inputs)) {
for (let i = 1; i < input.length; i++) {
const inputValue = input[i];
if (typeof inputValue === 'string') {
blockPool.addReference(inputValue);
}
}
}
}
}
blockPool.generateNewIds();
if (projectData.monitors) {
for (const monitor of projectData.monitors) {
// Remove redundant monitor values
monitor.value = Array.isArray(monitor.value) ? [] : 0;
}
}
// Use gathered data to optimize the project
for (const target of projectData.targets) {
const newBlocks = {};
const newComments = {};
for (const [blockId, block] of Object.entries(target.blocks)) {
newBlocks[blockPool.getNewId(blockId)] = block;
if (Array.isArray(block)) {
continue;
}
if (block.parent) {
block.parent = blockPool.getNewId(block.parent);
}
if (block.next) {
block.next = blockPool.getNewId(block.next);
}
for (const input of Object.values(block.inputs)) {
for (let i = 1; i < input.length; i++) {
const inputValue = input[i];
if (typeof inputValue === 'string') {
input[i] = blockPool.getNewId(inputValue);
}
}
}
if (!block.shadow) {
delete block.shadow;
}
if (!block.topLevel) {
delete block.topLevel;
}
delete block.x;
delete block.y;
delete block.comment;
}
for (const [commentId, comment] of Object.entries(target.comments)) {
const text = comment.text;
const isSpecial = text.includes(' // _twconfig_') || text.includes(' // _gamepad_');
if (isSpecial) {
newComments[commentId] = comment;
}
}
target.blocks = newBlocks;
target.comments = newComments;
}
// Remove unnecessary metadata
if (projectData.meta) {
delete projectData.meta.agent;
delete projectData.meta.vm;
}
return projectData;
};
export default optimizeSb3Json;
|