Skip to content

Commit 829714a

Browse files
committed
🚧 Fix multi-blueprint loading issues ( #207 )
1 parent 72423a7 commit 829714a

13 files changed

+324
-542
lines changed

src/blockbenchTypeMods.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ declare global {
3939
textDisplays: TextDisplay[]
4040
vanillaItemDisplays: VanillaItemDisplay[]
4141
vanillaBlockDisplays: VanillaBlockDisplay[]
42+
43+
loadingPromises?: Array<Promise<unknown>>
4244
}
4345

4446
// eslint-disable-next-line @typescript-eslint/naming-convention

src/blueprintFormat.ts

Lines changed: 62 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ export const BLUEPRINT_CODEC = new Blockbench.Codec('animated_java_blueprint', {
220220
console.log(`Parsing Animated Java Blueprint from '${path}'...`)
221221
if (!Project) throw new Error('No project to parse into')
222222

223+
Project.loadingPromises = []
224+
223225
Project.save_path = model.meta.save_location || path
224226

225227
if (model.meta.box_uv !== undefined) {
@@ -268,22 +270,32 @@ export const BLUEPRINT_CODEC = new Blockbench.Codec('animated_java_blueprint', {
268270
const defaultTexture = Texture.getDefault()
269271
for (const element of model.elements) {
270272
const newElement = OutlinerElement.fromSave(element, true)
271-
if (newElement instanceof Cube) {
272-
for (const face in newElement.faces) {
273-
if (element.faces) {
274-
const texture =
275-
element.faces[face].texture !== undefined &&
276-
Texture.all[element.faces[face].texture]
277-
if (texture) {
278-
newElement.faces[face].texture = texture.uuid
273+
switch (true) {
274+
case newElement instanceof Cube: {
275+
for (const face in newElement.faces) {
276+
if (element.faces) {
277+
const texture =
278+
element.faces[face].texture !== undefined &&
279+
Texture.all[element.faces[face].texture]
280+
if (texture) {
281+
newElement.faces[face].texture = texture.uuid
282+
}
283+
} else if (
284+
defaultTexture &&
285+
newElement.faces &&
286+
newElement.faces[face].texture !== undefined
287+
) {
288+
newElement.faces[face].texture = defaultTexture.uuid
279289
}
280-
} else if (
281-
defaultTexture &&
282-
newElement.faces &&
283-
newElement.faces[face].texture !== undefined
284-
) {
285-
newElement.faces[face].texture = defaultTexture.uuid
286290
}
291+
break
292+
}
293+
case newElement instanceof AnimatedJava.API.TextDisplay:
294+
case newElement instanceof AnimatedJava.API.VanillaItemDisplay:
295+
case newElement instanceof AnimatedJava.API.VanillaBlockDisplay: {
296+
// ES-Lint doesn't like the types here for some reason, so I'm casing them to please it.
297+
Project.loadingPromises.push(newElement.waitForReady() as Promise<void>)
298+
break
287299
}
288300
}
289301
}
@@ -537,39 +549,51 @@ export const BLUEPRINT_FORMAT = new Blockbench.ModelFormat({
537549
}
538550
}
539551

552+
const thisProject = Project
540553
Project.variants ??= []
541554
Project.last_used_export_namespace = Project.animated_java.export_namespace
542555
const updateBoundingBoxIntervalId = setInterval(() => {
543556
updateBoundingBox()
544557
}, 500)
545558
events.UNLOAD.subscribe(() => clearInterval(updateBoundingBoxIntervalId), true)
546559
events.UNINSTALL.subscribe(() => clearInterval(updateBoundingBoxIntervalId), true)
547-
requestAnimationFrame(() => {
548-
Project!.pluginMode = new Valuable(Project!.animated_java.enable_plugin_mode)
549-
// Remove the default title
550-
const element = document.querySelector('#tab_bar_list .icon-armor_stand.icon')
551-
element?.remove()
552-
// Custom title
553-
void injectSvelteCompomponent({
554-
elementSelector: () => {
555-
const titles = [
556-
...document.querySelectorAll(`.project_tab[title="${project.name}"]`),
557-
]
558-
if (titles.length) {
559-
return titles[0]
560-
}
561-
},
562-
prepend: true,
563-
svelteComponent: ProjectTitleSvelte,
564-
svelteComponentProperties: { pluginMode: Project!.pluginMode },
565-
})
566560

567-
Project!.materials[TRANSPARENT_TEXTURE.uuid] = TRANSPARENT_TEXTURE_MATERIAL
568-
TRANSPARENT_TEXTURE.updateMaterial()
561+
thisProject.materials[TRANSPARENT_TEXTURE.uuid] = TRANSPARENT_TEXTURE_MATERIAL
562+
TRANSPARENT_TEXTURE.updateMaterial()
569563

570-
if (Variant.all.length === 0) new Variant('Default', true)
571-
Variant.selectDefault()
572-
})
564+
Project.loadingPromises ??= []
565+
Project.loadingPromises.push(
566+
new Promise<void>(resolve => {
567+
requestAnimationFrame(() => {
568+
thisProject.pluginMode = new Valuable(
569+
thisProject.animated_java.enable_plugin_mode
570+
)
571+
// Remove the default title
572+
const element = document.querySelector('#tab_bar_list .icon-armor_stand.icon')
573+
element?.remove()
574+
// Custom title
575+
void injectSvelteCompomponent({
576+
elementSelector: () => {
577+
const titles = [
578+
...document.querySelectorAll(
579+
`.project_tab[title="${project.name}"]`
580+
),
581+
]
582+
if (titles.length) {
583+
return titles[0]
584+
}
585+
},
586+
prepend: true,
587+
svelteComponent: ProjectTitleSvelte,
588+
svelteComponentProperties: { pluginMode: thisProject.pluginMode },
589+
})
590+
591+
if (Variant.all.length === 0) new Variant('Default', true)
592+
Variant.selectDefault()
593+
})
594+
resolve()
595+
})
596+
)
573597
},
574598

575599
onActivation() {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<script lang="ts" context="module">
2+
import ArmorStandRunningGif from '../assets/armor_stand_running.webp'
3+
import { Valuable } from '../util/stores'
4+
</script>
5+
6+
<script lang="ts">
7+
export let progress: Valuable<number>
8+
export let maxProgress: Valuable<number>
9+
</script>
10+
11+
<div>
12+
<progress value={$progress} max={$maxProgress} />
13+
<!-- svelte-ignore a11y-missing-attribute -->
14+
<img src={ArmorStandRunningGif} width="64" />
15+
</div>
16+
17+
<style>
18+
div {
19+
display: flex;
20+
align-items: center;
21+
justify-content: center;
22+
}
23+
img {
24+
margin-left: 16px;
25+
}
26+
progress {
27+
flex-grow: 1;
28+
}
29+
</style>

src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import './mods/saveProjectAsActionMod'
4444
import './mods/variantPreviewCubeFaceMod'
4545
import './mods/showDefaultPoseMod'
4646
import './mods/addLocatorActionMod'
47+
import './mods/blockbenchReadMod'
4748
// Outliner
4849
import './outliner/textDisplay'
4950
import './outliner/vanillaItemDisplay'
@@ -81,6 +82,7 @@ import { VanillaItemDisplay } from './outliner/vanillaItemDisplay'
8182
import { VanillaBlockDisplay, debugBlockState, debugBlocks } from './outliner/vanillaBlockDisplay'
8283
import { BLOCKSTATE_REGISTRY } from './systems/minecraft/blockstateManager'
8384
import { exportProject } from './systems/exporter'
85+
import { openBlueprintLoadingDialog } from './interface/blueprintLoadingPopup'
8486

8587
// Show loading popup
8688
void showLoadingPopup().then(async () => {
@@ -135,6 +137,7 @@ globalThis.AnimatedJava = {
135137
debugBlockState,
136138
BLOCKSTATE_REGISTRY,
137139
exportProject,
140+
openBlueprintLoadingDialog,
138141
},
139142
}
140143

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import BlueprintLoadingPopup from '../components/blueprintLoadingPopup.svelte'
2+
import { PACKAGE } from '../constants'
3+
import { Valuable } from '../util/stores'
4+
import { SvelteDialog } from '../util/svelteDialog'
5+
import { translate } from '../util/translation'
6+
7+
export const PROGRESS = new Valuable(0)
8+
export const MAX_PROGRESS = new Valuable(1)
9+
10+
let instance: SvelteDialog | null = null
11+
12+
export function openBlueprintLoadingDialog() {
13+
PROGRESS.set(0)
14+
MAX_PROGRESS.set(1)
15+
16+
instance = new SvelteDialog({
17+
id: `${PACKAGE.name}:blueprintLoadingPopup`,
18+
title: translate('dialog.blueprint_loading.title'),
19+
width: 128,
20+
svelteComponent: BlueprintLoadingPopup,
21+
svelteComponentProperties: {
22+
progress: PROGRESS,
23+
maxProgress: MAX_PROGRESS,
24+
},
25+
preventKeybinds: true,
26+
buttons: [],
27+
}).show()
28+
return dialog
29+
}
30+
31+
export function closeBlueprintLoadingDialog() {
32+
if (instance) {
33+
instance.close(0)
34+
}
35+
instance = null
36+
}

src/lang/en.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,9 @@ animated_java.dialog.animation_properties.swap_columns_button.tooltip: Swap List
366366
## Export Progress Dialog
367367
animated_java.dialog.export_progress.title: Exporting...
368368

369+
## Blueprint Loading Dialog
370+
animated_java.dialog.blueprint_loading.title: Loading Blueprint...
371+
369372
### Panels
370373

371374
## Variants Panel

src/mods/blockbenchReadMod.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { PACKAGE } from '../constants'
2+
import {
3+
closeBlueprintLoadingDialog,
4+
openBlueprintLoadingDialog,
5+
PROGRESS,
6+
} from '../interface/blueprintLoadingPopup'
7+
import { createBlockbenchMod } from '../util/moddingTools'
8+
9+
createBlockbenchMod(
10+
`${PACKAGE.name}:blockbenchReadMod`,
11+
{
12+
original: Blockbench.read,
13+
},
14+
context => {
15+
async function asyncRead(
16+
files: Parameters<typeof Blockbench.read>['0'],
17+
options: Parameters<typeof Blockbench.read>['1'],
18+
cb: Parameters<typeof Blockbench.read>['2']
19+
) {
20+
for (const file of files) {
21+
context.original([file], options, cb)
22+
await new Promise<void>(r => {
23+
if (Project?.loadingPromises) {
24+
openBlueprintLoadingDialog()
25+
const promises: Array<Promise<unknown>> = []
26+
for (const promise of Project.loadingPromises) {
27+
promises.push(
28+
new Promise<void>(r => {
29+
promise.finally(() => {
30+
PROGRESS.set(PROGRESS.get() + 1)
31+
r()
32+
})
33+
})
34+
)
35+
}
36+
void Promise.all(promises).finally(() => {
37+
closeBlueprintLoadingDialog()
38+
r()
39+
})
40+
return
41+
}
42+
r()
43+
})
44+
}
45+
}
46+
47+
Blockbench.read = function (files, options, cb) {
48+
void asyncRead(files, options, cb).catch(console.error)
49+
}
50+
return context
51+
},
52+
context => {
53+
Blockbench.read = context.original
54+
}
55+
)

src/outliner/textDisplay.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ export class TextDisplay extends ResizableOutlinerElement {
317317

318318
async waitForReady() {
319319
while (!this.ready) {
320-
await new Promise(resolve => setTimeout(resolve, 100))
320+
await new Promise(resolve => setTimeout(resolve, 1000 / framespersecond))
321321
}
322322
}
323323

src/outliner/vanillaBlockDisplay.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ export class VanillaBlockDisplay extends ResizableOutlinerElement {
5353
// eslint-disable-next-line @typescript-eslint/naming-convention
5454
public preview_controller = PREVIEW_CONTROLLER
5555

56+
public ready = false
57+
5658
constructor(data: VanillaBlockDisplayOptions, uuid = guid()) {
5759
super(data, uuid)
5860
VanillaBlockDisplay.all.push(this)
@@ -105,6 +107,12 @@ export class VanillaBlockDisplay extends ResizableOutlinerElement {
105107
this._block.set(value)
106108
}
107109

110+
async waitForReady() {
111+
while (!this.ready) {
112+
await new Promise(resolve => setTimeout(resolve, 1000 / framespersecond))
113+
}
114+
}
115+
108116
public sanitizeName(): string {
109117
this.name = toSafeFuntionName(this.name)
110118
const otherNodes = [
@@ -232,6 +240,8 @@ export const PREVIEW_CONTROLLER = new NodePreviewController(VanillaBlockDisplay,
232240
el.preview_controller.updateTransform(el)
233241
el.mesh.visible = el.visibility
234242
TickUpdates.selection = true
243+
244+
el.ready = true
235245
})
236246
.catch(err => {
237247
console.error(err)

src/outliner/vanillaItemDisplay.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ export class VanillaItemDisplay extends ResizableOutlinerElement {
4949
// eslint-disable-next-line @typescript-eslint/naming-convention
5050
public preview_controller = PREVIEW_CONTROLLER
5151

52+
public ready = false
53+
5254
constructor(data: VanillaItemDisplayOptions, uuid = guid()) {
5355
super(data, uuid)
5456
VanillaItemDisplay.all.push(this)
@@ -104,6 +106,12 @@ export class VanillaItemDisplay extends ResizableOutlinerElement {
104106
this._item.set(value)
105107
}
106108

109+
async waitForReady() {
110+
while (!this.ready) {
111+
await new Promise(resolve => setTimeout(resolve, 1000 / framespersecond))
112+
}
113+
}
114+
107115
public sanitizeName(): string {
108116
this.name = toSafeFuntionName(this.name)
109117
const otherNodes = [
@@ -229,6 +237,8 @@ export const PREVIEW_CONTROLLER = new NodePreviewController(VanillaItemDisplay,
229237
el.preview_controller.updateTransform(el)
230238
el.mesh.visible = el.visibility
231239
TickUpdates.selection = true
240+
241+
el.ready = true
232242
})
233243
.catch(err => {
234244
if (typeof err.message === 'string') {

0 commit comments

Comments
 (0)