From ded9b75cb6c136eda6cc3b38910167793ce35f13 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 16 Feb 2023 19:58:40 -0400 Subject: [PATCH 01/13] Reference/resolve variables by ID --- src/BlockInput.ts | 10 +++- src/io/leopard/toLeopard.ts | 77 +++++++++++-------------- src/io/sb3/fromSb3.ts | 10 +++- src/io/sb3/toSb3.ts | 15 +++-- src/io/scratchblocks/toScratchblocks.ts | 2 + 5 files changed, 58 insertions(+), 56 deletions(-) diff --git a/src/BlockInput.ts b/src/BlockInput.ts index 1f3c12ac..8f8f4cd2 100644 --- a/src/BlockInput.ts +++ b/src/BlockInput.ts @@ -41,12 +41,18 @@ export interface Broadcast extends Base { export interface Variable extends Base { type: "variable"; - value: string; + value: { + id: string; + name: string; + }; } export interface List extends Base { type: "list"; - value: string; + value: { + id: string; + name: string; + }; } export interface Block extends Base { diff --git a/src/io/leopard/toLeopard.ts b/src/io/leopard/toLeopard.ts index 59235442..99dcc195 100644 --- a/src/io/leopard/toLeopard.ts +++ b/src/io/leopard/toLeopard.ts @@ -99,7 +99,7 @@ export default function toLeopard( let targetNameMap = {}; let customBlockArgNameMap: Map = new Map(); - let variableNameMap: Map = new Map(); + let variableNameMap: { [id: string]: string } = {}; // ID to unique (Leopard) name for (const target of [project.stage, ...project.sprites]) { const newTargetName = uniqueName(camelCase(target.name, true)); @@ -108,19 +108,9 @@ export default function toLeopard( let uniqueVariableName = uniqueName.branch(); - const varNameMap = {}; - variableNameMap.set(target, varNameMap); - - for (const list of target.lists) { - const newName = uniqueVariableName(camelCase(list.name)); - varNameMap[list.name] = newName; - list.setName(newName); - } - - for (const variable of target.variables) { - const newName = uniqueVariableName(camelCase(variable.name)); - varNameMap[variable.name] = newName; - variable.setName(newName); + for (const { id, name } of [...target.lists, ...target.variables]) { + const newName = uniqueVariableName(camelCase(name)); + variableNameMap[id] = newName; } const uniqueScriptName = uniqueNameGenerator([ @@ -276,24 +266,25 @@ export default function toLeopard( // If the block contains a variable or list dropdown, // get the code to grab that variable now for convenience - let varName: string = null; let selectedVarSource: string = null; let selectedWatcherSource: string = null; + let varInputId: string = null; + let isSpriteVar: boolean = null; if ("VARIABLE" in block.inputs) { - varName = block.inputs.VARIABLE.value.toString(); + varInputId = (block.inputs.VARIABLE.value as { id: string }).id; + isSpriteVar = target.variables.some(({ id }) => id === varInputId); + } else if ("LIST" in block.inputs) { + varInputId = (block.inputs.LIST.value as { id: string }).id; + isSpriteVar = target.lists.some(({ id }) => id === varInputId); } - if ("LIST" in block.inputs) { - varName = block.inputs.LIST.value.toString(); - } - if (varName !== null) { - const spriteVars = variableNameMap.get(target); - if (varName in spriteVars) { - selectedVarSource = `this.vars.${spriteVars[varName]}`; - selectedWatcherSource = `this.watchers.${spriteVars[varName]}`; + if (varInputId) { + const newName = variableNameMap[varInputId]; + if (isSpriteVar) { + selectedVarSource = `this.vars.${newName}`; + selectedWatcherSource = `this.watchers.${newName}`; } else { - const stageVars = variableNameMap.get(project.stage); - selectedVarSource = `this.stage.vars.${stageVars[varName]}`; - selectedWatcherSource = `this.stage.watchers.${stageVars[varName]}`; + selectedVarSource = `this.stage.vars.${newName}`; + selectedWatcherSource = `this.stage.watchers.${newName}`; } } @@ -643,7 +634,10 @@ export default function toLeopard( if (block.inputs.OBJECT.value !== "_stage_") { varOwner = project.sprites.find(sprite => sprite.name === targetNameMap[block.inputs.OBJECT.value]); } - propName = `vars[${JSON.stringify(variableNameMap.get(varOwner)[block.inputs.PROPERTY.value])}]`; + // "of" block gets variables by name, not ID, using lookupVariableByNameAndType in scratch-vm. + const variable = varOwner.variables.find(variable => variable.name === block.inputs.PROPERTY.value); + const newName = variableNameMap[variable.id]; + propName = `vars.${newName}`; break; } } @@ -976,7 +970,7 @@ export default function toLeopard( // Some watchers start invisible but appear later, so this code builds a list of // watchers that appear in "show variable" and "show list" blocks. The list is // actually *used* later, by some other code. - let shownWatchers = new Set(); + let shownWatchers: Set = new Set(); let targetsToCheckForShowBlocks: Target[]; if (target.isStage) { targetsToCheckForShowBlocks = [project.stage, ...project.sprites]; @@ -1051,32 +1045,29 @@ export default function toLeopard( ${target.volume !== 100 ? `this.audioEffects.volume = ${target.volume};` : ""} - ${[...target.variables, ...target.lists] - .map(variable => `this.vars.${variable.name} = ${toOptimalJavascriptRepresentation(variable.value)};`) - .join("\n")} - ${[...target.variables, ...target.lists] .map( variable => - [ - variable, - Object.entries(variableNameMap.get(target)).find(([, newName]) => newName === variable.name)[0] - ] as [Variable | List, string] + `this.vars.${variableNameMap[variable.id]} = ${toOptimalJavascriptRepresentation(variable.value)};` ) - .filter(([variable, oldName]) => variable.visible || shownWatchers.has(oldName)) - .map(([variable, oldName]) => { - return `this.watchers.${variable.name} = new Watcher({ - label: ${JSON.stringify((target.isStage ? "" : `${target.name}: `) + oldName)}, + .join("\n")} + + ${[...target.variables, ...target.lists] + .map(variable => [variable, variableNameMap[variable.id]] as [Variable | List, string]) + .filter(([variable]) => variable.visible || shownWatchers.has(variable.id)) + .map(([variable, newName]) => { + return `this.watchers.${newName} = new Watcher({ + label: ${JSON.stringify((target.isStage ? "" : `${target.name}: `) + variable.name)}, style: ${JSON.stringify( variable instanceof List ? "normal" : { default: "normal", large: "large", slider: "slider" }[variable.mode] )}, visible: ${JSON.stringify(variable.visible)}, - value: () => this.vars.${variable.name}, + value: () => this.vars.${newName}, ${ variable instanceof Variable && variable.mode === "slider" - ? `setValue: (value) => { this.vars.${variable.name} = value; },\n` + ? `setValue: (value) => { this.vars.${newName} = value; },\n` : "" }x: ${JSON.stringify(variable.x + 240)}, y: ${JSON.stringify(180 - variable.y)}, diff --git a/src/io/sb3/fromSb3.ts b/src/io/sb3/fromSb3.ts index 52bd73b5..5e19ca7f 100644 --- a/src/io/sb3/fromSb3.ts +++ b/src/io/sb3/fromSb3.ts @@ -216,7 +216,7 @@ function getBlockScript(blocks: { [key: string]: sb3.Block }) { type: "block", value: new BlockBase({ opcode: OpCode.data_variable, - inputs: { VARIABLE: { type: "variable", value: value[1] } }, + inputs: { VARIABLE: { type: "variable", value: { id: value[2], name: value[1] } } }, parent: blockId }) as Block }); @@ -227,7 +227,7 @@ function getBlockScript(blocks: { [key: string]: sb3.Block }) { type: "block", value: new BlockBase({ opcode: OpCode.data_listcontents, - inputs: { LIST: { type: "list", value: value[1] } }, + inputs: { LIST: { type: "list", value: { id: value[2], name: value[1] } } }, parent: blockId }) as Block }); @@ -268,7 +268,11 @@ function getBlockScript(blocks: { [key: string]: sb3.Block }) { let result = {}; for (const [fieldName, values] of Object.entries(fields)) { const type = sb3.fieldTypeMap[opcode][fieldName]; - result[fieldName] = { type, value: values[0] }; + if (fieldName === "VARIABLE" || fieldName === "LIST") { + result[fieldName] = { type, value: { id: values[1], name: values[0] } }; + } else { + result[fieldName] = { type, value: values[0] }; + } } return result; diff --git a/src/io/sb3/toSb3.ts b/src/io/sb3/toSb3.ts index 10533a1f..03bceec9 100644 --- a/src/io/sb3/toSb3.ts +++ b/src/io/sb3/toSb3.ts @@ -93,19 +93,20 @@ export default function toSb3(options: Partial = {}): ToSb3Output for (const key of Object.keys(fieldEntries)) { const input = inputs[key]; // Fields are stored as a plain [value, id?] pair. + let valueOrName; let id: string; switch (input.type) { case "variable": - id = getVariableId(input.value, target, stage); - break; case "list": - id = getListId(input.value, target, stage); + valueOrName = input.value.name; + id = input.value.id; break; default: + valueOrName = input.value; id = null; break; } - fields[key] = [input.value, id]; + fields[key] = [valueOrName, id]; } return fields; @@ -397,14 +398,12 @@ export default function toSb3(options: Partial = {}): ToSb3Output switch (input.value.opcode) { case OpCode.data_variable: { - const variableName = input.value.inputs.VARIABLE.value; - const variableId = getVariableId(variableName, target, stage); + const { id: variableId, name: variableName } = input.value.inputs.VARIABLE.value; obscuringBlockValue = [BIS.VAR_PRIMITIVE, variableName, variableId]; break; } case OpCode.data_listcontents: { - const listName = input.value.inputs.LIST.value; - const listId = getListId(listName, target, stage); + const { id: listId, name: listName } = input.value.inputs.LIST.value; obscuringBlockValue = [BIS.LIST_PRIMITIVE, listName, listId]; break; } diff --git a/src/io/scratchblocks/toScratchblocks.ts b/src/io/scratchblocks/toScratchblocks.ts index 12155f78..74bfa575 100644 --- a/src/io/scratchblocks/toScratchblocks.ts +++ b/src/io/scratchblocks/toScratchblocks.ts @@ -61,6 +61,8 @@ export default function toScratchblocks( case "variable": case "list": + return `[${escape(inp.value.name)} v]`; + case "rotationStyle": case "scrollAlignment": case "stopMenu": From fc1d20853d1d5a8072a56871c42954f479b42732 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 16 Feb 2023 20:02:01 -0400 Subject: [PATCH 02/13] Include watchers from hide procs in shownWatchers --- src/io/leopard/toLeopard.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/io/leopard/toLeopard.ts b/src/io/leopard/toLeopard.ts index 99dcc195..3c243913 100644 --- a/src/io/leopard/toLeopard.ts +++ b/src/io/leopard/toLeopard.ts @@ -980,11 +980,11 @@ export default function toLeopard( for (const checkTarget of targetsToCheckForShowBlocks) { for (const script of checkTarget.scripts) { for (const block of script.blocks) { - if (block.opcode === OpCode.data_showvariable) { - shownWatchers.add(block.inputs.VARIABLE.value); + if (block.opcode === OpCode.data_showvariable || block.opcode === OpCode.data_hidevariable) { + shownWatchers.add(block.inputs.VARIABLE.value.id); } - if (block.opcode === OpCode.data_showlist) { - shownWatchers.add(block.inputs.LIST.value); + if (block.opcode === OpCode.data_showlist || block.opcode === OpCode.data_hidelist) { + shownWatchers.add(block.inputs.LIST.value.id); } } } From c28eb9483103f468f716c8ce88e697c6f777e43b Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Thu, 16 Feb 2023 23:36:38 -0400 Subject: [PATCH 03/13] fix opcode error because thanks typescript --- src/io/leopard/toLeopard.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/io/leopard/toLeopard.ts b/src/io/leopard/toLeopard.ts index 3c243913..4cc20784 100644 --- a/src/io/leopard/toLeopard.ts +++ b/src/io/leopard/toLeopard.ts @@ -1,6 +1,6 @@ import Project from "../../Project"; import Script from "../../Script"; -import Block from "../../Block"; +import Block, { BlockBase } from "../../Block"; import * as BlockInput from "../../BlockInput"; import { OpCode } from "../../OpCode"; @@ -981,10 +981,14 @@ export default function toLeopard( for (const script of checkTarget.scripts) { for (const block of script.blocks) { if (block.opcode === OpCode.data_showvariable || block.opcode === OpCode.data_hidevariable) { - shownWatchers.add(block.inputs.VARIABLE.value.id); + shownWatchers.add( + (block as BlockBase).inputs.VARIABLE.value.id + ); } if (block.opcode === OpCode.data_showlist || block.opcode === OpCode.data_hidelist) { - shownWatchers.add(block.inputs.LIST.value.id); + shownWatchers.add( + (block as BlockBase).inputs.LIST.value.id + ); } } } From af5280ba271a184aa0a218ab7b306a7f2f31377c Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 17 Feb 2023 00:10:42 -0400 Subject: [PATCH 04/13] fromSb3: extra pass to clear unused variables --- src/io/sb3/fromSb3.ts | 68 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/src/io/sb3/fromSb3.ts b/src/io/sb3/fromSb3.ts index 5e19ca7f..d19dd8dc 100644 --- a/src/io/sb3/fromSb3.ts +++ b/src/io/sb3/fromSb3.ts @@ -8,7 +8,7 @@ import * as BlockInput from "../../BlockInput"; import Costume from "../../Costume"; import Project from "../../Project"; import Sound from "../../Sound"; -import { Sprite, Stage, TargetOptions } from "../../Target"; +import Target, { Sprite, Stage, TargetOptions } from "../../Target"; import { List, Variable } from "../../Data"; import Script from "../../Script"; @@ -400,7 +400,7 @@ export async function fromSb3JSON(json: sb3.ProjectJSON, options: { getAsset: Ge }; } - return new Project({ + const project = new Project({ stage: new Stage(await getTargetOptions(stage)), sprites: await Promise.all( json.targets @@ -427,6 +427,70 @@ export async function fromSb3JSON(json: sb3.ProjectJSON, options: { getAsset: Ge videoOn: stage.videoState === "on", videoAlpha: stage.videoTransparency }); + + const targetToBlocks: Map> = new Map(); + for (const target of [project.stage, ...project.sprites]) { + targetToBlocks.set(target, flattenBlocks(target.scripts.flatMap(script => script.blocks))); + } + const allBlocks = Array.from(targetToBlocks.values()).flat(); + + // Run an extra pass on variables (and lists). Only those which are actually + // referenced in blocks or monitors should be kept. + for (const target of [project.stage, ...project.sprites]) { + let relevantBlocks: Array = null; + if (target === project.stage) { + relevantBlocks = allBlocks; + } else { + relevantBlocks = targetToBlocks.get(target); + } + + const usedVariableIds: Set = new Set(); + for (const block of relevantBlocks) { + let id: string = null; + if ((block.inputs as { VARIABLE: BlockInput.Variable }).VARIABLE) { + id = (block.inputs as { VARIABLE: BlockInput.Variable }).VARIABLE.value.id; + } else if ((block.inputs as { LIST: BlockInput.List }).LIST) { + id = (block.inputs as { LIST: BlockInput.List }).LIST.value.id; + } else { + continue; + } + usedVariableIds.add(id); + } + + for (const varList of [target.variables, target.lists]) { + for (let i = 0, variable; variable = varList[i]; i++) { + if (variable.visible) { + continue; + } + if (usedVariableIds.has(variable.id)) { + continue; + } + + varList.splice(i, 1); + i--; + continue; + } + } + } + + return project; +} + +function flattenBlocks(blocks: Array) { + return blocks.flatMap(block => [ + block, + ...flattenBlocks( + Object.values(block.inputs).flatMap(input => { + if (input.type === "block") { + return [input.value]; + } else if (input.type === "blocks") { + return input.value; + } else { + return []; + } + }) + ) + ]); } export default async function fromSb3(fileData: Parameters[0]): Promise { From 3a24a37230f98504d8a82d7f28a4dc6c1fb1f1f8 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 17 Feb 2023 00:17:06 -0400 Subject: [PATCH 05/13] lint --- src/io/sb3/fromSb3.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io/sb3/fromSb3.ts b/src/io/sb3/fromSb3.ts index d19dd8dc..fa068620 100644 --- a/src/io/sb3/fromSb3.ts +++ b/src/io/sb3/fromSb3.ts @@ -458,7 +458,7 @@ export async function fromSb3JSON(json: sb3.ProjectJSON, options: { getAsset: Ge } for (const varList of [target.variables, target.lists]) { - for (let i = 0, variable; variable = varList[i]; i++) { + for (let i = 0, variable; (variable = varList[i]); i++) { if (variable.visible) { continue; } From 6636ff8a9591505f3872819386ddeccc25102fb5 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 19 Feb 2023 07:51:15 -0400 Subject: [PATCH 06/13] toScratchblocks: fix variable/list reporters --- src/io/scratchblocks/toScratchblocks.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/io/scratchblocks/toScratchblocks.ts b/src/io/scratchblocks/toScratchblocks.ts index 74bfa575..bfcc1d11 100644 --- a/src/io/scratchblocks/toScratchblocks.ts +++ b/src/io/scratchblocks/toScratchblocks.ts @@ -424,7 +424,7 @@ export default function toScratchblocks( // data -------------------------------------------------------- // case OpCode.data_variable: - return `(${block.inputs.VARIABLE.value} :: variables)`; + return `(${block.inputs.VARIABLE.value.name} :: variables)`; case OpCode.data_setvariableto: return `set ${i("VARIABLE")} to ${i("VALUE")}`; case OpCode.data_changevariableby: @@ -434,7 +434,7 @@ export default function toScratchblocks( case OpCode.data_hidevariable: return `hide variable ${i("VARIABLE")}`; case OpCode.data_listcontents: - return `(${block.inputs.LIST.value} :: list)`; + return `(${block.inputs.LIST.value.name} :: list)`; case OpCode.data_addtolist: return `add ${i("ITEM")} to ${i("LIST")}`; case OpCode.data_deleteoflist: From 3f9f401090b8a8080b30cb6bcc4f8fc1ac74ec96 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 19 Feb 2023 22:35:47 -0400 Subject: [PATCH 07/13] toLeopard: update example-project snapshot test --- src/__tests__/__snapshots__/compilesb3.test.ts.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/__snapshots__/compilesb3.test.ts.snap b/src/__tests__/__snapshots__/compilesb3.test.ts.snap index 379415b0..5ee02e12 100644 --- a/src/__tests__/__snapshots__/compilesb3.test.ts.snap +++ b/src/__tests__/__snapshots__/compilesb3.test.ts.snap @@ -168,7 +168,7 @@ export default class Stage extends StageBase { yield* this.askAndWait(this.loudness); yield* this.askAndWait(this.timer); yield* this.askAndWait(this.stage.costumeNumber); - yield* this.askAndWait(this.stage.vars[\\"CloudVar\\"]); + yield* this.askAndWait(this.stage.vars.CloudVar); yield* this.askAndWait(new Date().getDay() + 1); yield* this.askAndWait( ((new Date().getTime() - new Date(2000, 0, 1)) / 1000 / 60 + From 037c676c865543e30dd8f30aaebf1fcddb0a58b1 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 19 Feb 2023 18:40:45 -0400 Subject: [PATCH 08/13] Target.blocks - flatten all the way down ...and then use this property where appropriate --- src/Target.ts | 18 +++++++++++++++--- src/io/leopard/toLeopard.ts | 22 ++++++++++------------ src/io/sb3/fromSb3.ts | 28 +++------------------------- 3 files changed, 28 insertions(+), 40 deletions(-) diff --git a/src/Target.ts b/src/Target.ts index 54a2ce28..2cb4c267 100644 --- a/src/Target.ts +++ b/src/Target.ts @@ -25,9 +25,21 @@ export default class Target { } public get blocks(): Block[] { - return this.scripts.flatMap(script => { - return script.blocks.flatMap(block => block.blocks); - }); + return this.scripts.flatMap(script => + script.blocks.flatMap(recursive)); + + function recursive(block: Block): Block[] { + return [block, ...Object.values(block.inputs).flatMap(input => { + switch (input.type) { + case "block": + return recursive(input.value); + case "blocks": + return input.value.flatMap(recursive); + default: + return []; + } + })]; + } } public setName(name: string): void { diff --git a/src/io/leopard/toLeopard.ts b/src/io/leopard/toLeopard.ts index 4cc20784..994fa600 100644 --- a/src/io/leopard/toLeopard.ts +++ b/src/io/leopard/toLeopard.ts @@ -978,18 +978,16 @@ export default function toLeopard( targetsToCheckForShowBlocks = [target]; } for (const checkTarget of targetsToCheckForShowBlocks) { - for (const script of checkTarget.scripts) { - for (const block of script.blocks) { - if (block.opcode === OpCode.data_showvariable || block.opcode === OpCode.data_hidevariable) { - shownWatchers.add( - (block as BlockBase).inputs.VARIABLE.value.id - ); - } - if (block.opcode === OpCode.data_showlist || block.opcode === OpCode.data_hidelist) { - shownWatchers.add( - (block as BlockBase).inputs.LIST.value.id - ); - } + for (const block of checkTarget.blocks) { + if (block.opcode === OpCode.data_showvariable || block.opcode === OpCode.data_hidevariable) { + shownWatchers.add( + (block as BlockBase).inputs.VARIABLE.value.id + ); + } + if (block.opcode === OpCode.data_showlist || block.opcode === OpCode.data_hidelist) { + shownWatchers.add( + (block as BlockBase).inputs.LIST.value.id + ); } } } diff --git a/src/io/sb3/fromSb3.ts b/src/io/sb3/fromSb3.ts index fa068620..2b9fd2d7 100644 --- a/src/io/sb3/fromSb3.ts +++ b/src/io/sb3/fromSb3.ts @@ -428,20 +428,15 @@ export async function fromSb3JSON(json: sb3.ProjectJSON, options: { getAsset: Ge videoAlpha: stage.videoTransparency }); - const targetToBlocks: Map> = new Map(); - for (const target of [project.stage, ...project.sprites]) { - targetToBlocks.set(target, flattenBlocks(target.scripts.flatMap(script => script.blocks))); - } - const allBlocks = Array.from(targetToBlocks.values()).flat(); - // Run an extra pass on variables (and lists). Only those which are actually // referenced in blocks or monitors should be kept. for (const target of [project.stage, ...project.sprites]) { let relevantBlocks: Array = null; if (target === project.stage) { - relevantBlocks = allBlocks; + relevantBlocks = [project.stage, ...project.sprites] + .flatMap(target => target.blocks); } else { - relevantBlocks = targetToBlocks.get(target); + relevantBlocks = target.blocks; } const usedVariableIds: Set = new Set(); @@ -476,23 +471,6 @@ export async function fromSb3JSON(json: sb3.ProjectJSON, options: { getAsset: Ge return project; } -function flattenBlocks(blocks: Array) { - return blocks.flatMap(block => [ - block, - ...flattenBlocks( - Object.values(block.inputs).flatMap(input => { - if (input.type === "block") { - return [input.value]; - } else if (input.type === "blocks") { - return input.value; - } else { - return []; - } - }) - ) - ]); -} - export default async function fromSb3(fileData: Parameters[0]): Promise { const inZip = await JSZip.loadAsync(fileData); const json = await inZip.file("project.json").async("text"); From 1c57be300548cff27e49444c4029580ee511e758 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 26 Feb 2023 07:47:47 -0400 Subject: [PATCH 09/13] review changes & optmize Target.blocks --- src/Target.ts | 29 ++++++++++++++++++++--------- src/io/leopard/toLeopard.ts | 14 +++++--------- src/io/sb3/fromSb3.ts | 1 - 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/Target.ts b/src/Target.ts index 2cb4c267..d4b11954 100644 --- a/src/Target.ts +++ b/src/Target.ts @@ -25,20 +25,31 @@ export default class Target { } public get blocks(): Block[] { - return this.scripts.flatMap(script => - script.blocks.flatMap(recursive)); + const collector = []; - function recursive(block: Block): Block[] { - return [block, ...Object.values(block.inputs).flatMap(input => { + for (const script of this.scripts) { + for (const block of script.blocks) { + recursive(block); + } + } + + return collector; + + function recursive(block: Block) { + collector.push(block); + + for (const input of Object.values(block.inputs)) { switch (input.type) { case "block": - return recursive(input.value); + recursive(input.value); + break; case "blocks": - return input.value.flatMap(recursive); - default: - return []; + for (const block of input.value) { + recursive(block); + } + break; } - })]; + } } } diff --git a/src/io/leopard/toLeopard.ts b/src/io/leopard/toLeopard.ts index 994fa600..bc237a37 100644 --- a/src/io/leopard/toLeopard.ts +++ b/src/io/leopard/toLeopard.ts @@ -980,14 +980,10 @@ export default function toLeopard( for (const checkTarget of targetsToCheckForShowBlocks) { for (const block of checkTarget.blocks) { if (block.opcode === OpCode.data_showvariable || block.opcode === OpCode.data_hidevariable) { - shownWatchers.add( - (block as BlockBase).inputs.VARIABLE.value.id - ); + shownWatchers.add(block.inputs.VARIABLE.value.id); } if (block.opcode === OpCode.data_showlist || block.opcode === OpCode.data_hidelist) { - shownWatchers.add( - (block as BlockBase).inputs.LIST.value.id - ); + shownWatchers.add(block.inputs.LIST.value.id); } } } @@ -1055,9 +1051,9 @@ export default function toLeopard( .join("\n")} ${[...target.variables, ...target.lists] - .map(variable => [variable, variableNameMap[variable.id]] as [Variable | List, string]) - .filter(([variable]) => variable.visible || shownWatchers.has(variable.id)) - .map(([variable, newName]) => { + .filter(variable => variable.visible || shownWatchers.has(variable.id)) + .map(variable => { + const newName = variableNameMap[variable.id]; return `this.watchers.${newName} = new Watcher({ label: ${JSON.stringify((target.isStage ? "" : `${target.name}: `) + variable.name)}, style: ${JSON.stringify( diff --git a/src/io/sb3/fromSb3.ts b/src/io/sb3/fromSb3.ts index 2b9fd2d7..16485a5b 100644 --- a/src/io/sb3/fromSb3.ts +++ b/src/io/sb3/fromSb3.ts @@ -463,7 +463,6 @@ export async function fromSb3JSON(json: sb3.ProjectJSON, options: { getAsset: Ge varList.splice(i, 1); i--; - continue; } } } From fe88ff72dcb68b647843621585b4841382bf1e63 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 19 Feb 2023 18:54:37 -0400 Subject: [PATCH 10/13] Array -> T[] cleanup --- src/io/sb3/fromSb3.ts | 2 +- src/io/sb3/toSb3.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/io/sb3/fromSb3.ts b/src/io/sb3/fromSb3.ts index 16485a5b..fcafad30 100644 --- a/src/io/sb3/fromSb3.ts +++ b/src/io/sb3/fromSb3.ts @@ -431,7 +431,7 @@ export async function fromSb3JSON(json: sb3.ProjectJSON, options: { getAsset: Ge // Run an extra pass on variables (and lists). Only those which are actually // referenced in blocks or monitors should be kept. for (const target of [project.stage, ...project.sprites]) { - let relevantBlocks: Array = null; + let relevantBlocks: Block[] = null; if (target === project.stage) { relevantBlocks = [project.stage, ...project.sprites] .flatMap(target => target.blocks); diff --git a/src/io/sb3/toSb3.ts b/src/io/sb3/toSb3.ts index 03bceec9..75c31583 100644 --- a/src/io/sb3/toSb3.ts +++ b/src/io/sb3/toSb3.ts @@ -880,7 +880,7 @@ export default function toSb3(options: Partial = {}): ToSb3Output // etc into the structures Scratch 3.0 expects. function mapToIdObject( - values: Array, + values: Entry[], fn: (x: Entry) => ReturnType ): { [key: string]: ReturnType } { // Map an Array of objects with an "id` property From 693b11b77397f447b60a2fbd805466d0cfe9c054 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 26 Feb 2023 07:53:52 -0400 Subject: [PATCH 11/13] avoid one intermediate array --- src/io/sb3/fromSb3.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/io/sb3/fromSb3.ts b/src/io/sb3/fromSb3.ts index fcafad30..fc6c81ce 100644 --- a/src/io/sb3/fromSb3.ts +++ b/src/io/sb3/fromSb3.ts @@ -433,8 +433,7 @@ export async function fromSb3JSON(json: sb3.ProjectJSON, options: { getAsset: Ge for (const target of [project.stage, ...project.sprites]) { let relevantBlocks: Block[] = null; if (target === project.stage) { - relevantBlocks = [project.stage, ...project.sprites] - .flatMap(target => target.blocks); + relevantBlocks = project.stage.blocks.concat(project.sprites.flatMap(sprite => sprite.blocks)); } else { relevantBlocks = target.blocks; } From 36f093413e18a099f8d186e74df377a682cdfe0d Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 26 Feb 2023 08:13:15 -0400 Subject: [PATCH 12/13] toLeopard: cache list of stage variables --- src/io/leopard/toLeopard.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/io/leopard/toLeopard.ts b/src/io/leopard/toLeopard.ts index bc237a37..053fd157 100644 --- a/src/io/leopard/toLeopard.ts +++ b/src/io/leopard/toLeopard.ts @@ -161,6 +161,19 @@ export default function toLeopard( } } + // Cache a set of variables which are for the stage since whether or not a variable + // is local has to be known every time any variable block is converted. We check the + // stage because all non-stage variables are "for this sprite only" and because it's + // marginally quicker to iterate over a shorter set than a longer one [an assumption + // made about projects with primarily "for this sprite only" variables]. + const stageVariables: Set = new Set(); + for (const variable of project.stage.variables) { + stageVariables.add(variable.id); + } + for (const list of project.stage.lists) { + stageVariables.add(list.id); + } + function staticBlockInputToLiteral(value: string | number | boolean | object): string { const asNum = Number(value as string); if (!isNaN(asNum) && value !== "") { @@ -269,17 +282,14 @@ export default function toLeopard( let selectedVarSource: string = null; let selectedWatcherSource: string = null; let varInputId: string = null; - let isSpriteVar: boolean = null; if ("VARIABLE" in block.inputs) { varInputId = (block.inputs.VARIABLE.value as { id: string }).id; - isSpriteVar = target.variables.some(({ id }) => id === varInputId); } else if ("LIST" in block.inputs) { varInputId = (block.inputs.LIST.value as { id: string }).id; - isSpriteVar = target.lists.some(({ id }) => id === varInputId); } if (varInputId) { const newName = variableNameMap[varInputId]; - if (isSpriteVar) { + if (target === project.stage || !stageVariables.has(newName)) { selectedVarSource = `this.vars.${newName}`; selectedWatcherSource = `this.watchers.${newName}`; } else { From cf9f63406eab48c13a7d08aa20df1e93a3448355 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 26 Feb 2023 08:27:07 -0400 Subject: [PATCH 13/13] avoid redundant project.stage access --- src/io/sb3/fromSb3.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io/sb3/fromSb3.ts b/src/io/sb3/fromSb3.ts index fc6c81ce..d81d8c81 100644 --- a/src/io/sb3/fromSb3.ts +++ b/src/io/sb3/fromSb3.ts @@ -433,7 +433,7 @@ export async function fromSb3JSON(json: sb3.ProjectJSON, options: { getAsset: Ge for (const target of [project.stage, ...project.sprites]) { let relevantBlocks: Block[] = null; if (target === project.stage) { - relevantBlocks = project.stage.blocks.concat(project.sprites.flatMap(sprite => sprite.blocks)); + relevantBlocks = target.blocks.concat(project.sprites.flatMap(sprite => sprite.blocks)); } else { relevantBlocks = target.blocks; }