fix(perf): dont load gui textures on panorama start in singlefile build

fix: update textures in inventory & hotbar after textures load, including jei
fix: one row of jei was out of the screen
This commit is contained in:
Vitaly Turovsky 2025-03-31 13:16:57 +03:00
commit 4f45cd072a
9 changed files with 68 additions and 48 deletions

View file

@ -150,7 +150,7 @@
"http-browserify": "^1.7.0",
"http-server": "^14.1.1",
"https-browserify": "^1.0.0",
"mc-assets": "^0.2.48",
"mc-assets": "^0.2.49",
"mineflayer-mouse": "^0.1.7",
"minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next",
"mineflayer": "github:zardoy/mineflayer",

18
pnpm-lock.yaml generated
View file

@ -353,11 +353,11 @@ importers:
specifier: ^1.0.0
version: 1.0.0
mc-assets:
specifier: ^0.2.48
version: 0.2.48
specifier: ^0.2.49
version: 0.2.49
minecraft-inventory-gui:
specifier: github:zardoy/minecraft-inventory-gui#next
version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/69dee6aa15f7c8a6df2a2ab01972e6a7b2f9ee30(@types/react@18.2.20)(react@18.2.0)
version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/f57dd78ca8e3b7cdd724d4272d8cbf6743b0cf00(@types/react@18.2.20)(react@18.2.0)
mineflayer:
specifier: github:zardoy/mineflayer
version: https://codeload.github.com/zardoy/mineflayer/tar.gz/06e3050ddf4d9aa655fea6e2bed182937a81705d(encoding@0.1.13)
@ -6694,8 +6694,8 @@ packages:
maxrects-packer@2.7.3:
resolution: {integrity: sha512-bG6qXujJ1QgttZVIH4WDanhoJtvbud/xP/XPyf6A69C9RdA61BM4TomFALCq2nrTa+tARRIBB4LuIFsnUQU2wA==}
mc-assets@0.2.48:
resolution: {integrity: sha512-ixFBAkdWuluBZ3RhWXvD+KyLX5jKAK8ksXJamAuJxc7nXHP6xK5rEAR3qQ7JVYh27USl3mE+GWTFy/aF0CGRYg==}
mc-assets@0.2.49:
resolution: {integrity: sha512-pFR43FqG1bxiQVLXX8c4LNA/7wcFRGDFFclsBM6tY8tKFNolKhwiSaLmV0xtFHEF6SZGv9g2hkMqxsbAzMD/6A==}
engines: {node: '>=18.0.0'}
mcraft-fun-mineflayer@0.1.14:
@ -6912,8 +6912,8 @@ packages:
minecraft-folder-path@1.2.0:
resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==}
minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/69dee6aa15f7c8a6df2a2ab01972e6a7b2f9ee30:
resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/69dee6aa15f7c8a6df2a2ab01972e6a7b2f9ee30}
minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/f57dd78ca8e3b7cdd724d4272d8cbf6743b0cf00:
resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/f57dd78ca8e3b7cdd724d4272d8cbf6743b0cf00}
version: 1.0.1
minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/3bd4dc1b2002cd7badfa5b9cf8dda35cd6cc9ac1:
@ -17605,7 +17605,7 @@ snapshots:
maxrects-packer@2.7.3: {}
mc-assets@0.2.48:
mc-assets@0.2.49:
dependencies:
maxrects-packer: 2.7.3
zod: 3.24.1
@ -17922,7 +17922,7 @@ snapshots:
minecraft-folder-path@1.2.0: {}
minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/69dee6aa15f7c8a6df2a2ab01972e6a7b2f9ee30(@types/react@18.2.20)(react@18.2.0):
minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/f57dd78ca8e3b7cdd724d4272d8cbf6743b0cf00(@types/react@18.2.20)(react@18.2.0):
dependencies:
valtio: 1.11.2(@types/react@18.2.20)(react@18.2.0)
transitivePeerDependencies:

View file

@ -11,6 +11,7 @@ import { getItemDefinition } from 'mc-assets/dist/itemDefinitions'
export const activeGuiAtlas = proxy({
atlas: null as null | { json, image },
version: 0
})
export const getNonFullBlocksModels = () => {
@ -278,5 +279,6 @@ export const generateGuiAtlas = async () => {
const itemImages = await generateItemsGui(itemsModelsResolved, true)
console.timeEnd('generate items gui atlas')
await generateAtlas({ ...blockImages, ...itemImages })
activeGuiAtlas.version++
// await generateAtlas(blockImages)
}

View file

@ -183,7 +183,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
})
if (this.wasChunkSentToWorker(chunkKey)) {
const [x, y, z] = blockPos.split(',').map(Number)
this.setBlockStateId(new Vec3(x, y, z), undefined, false)
this.setBlockStateId(new Vec3(x, y, z), undefined)
}
}
@ -722,7 +722,7 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
abstract worldStop? ()
queueAwaited = false
messagesQueue = {} as { [workerIndex: string]: any[] }
toWorkerMessagesQueue = {} as { [workerIndex: string]: any[] }
getWorkerNumber (pos: Vec3, updateAction = false) {
if (updateAction) {
@ -749,8 +749,8 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
// is always dispatched to the same worker
const hash = this.getWorkerNumber(pos, useChangeWorker)
this.sectionsWaiting.set(key, (this.sectionsWaiting.get(key) ?? 0) + 1)
this.messagesQueue[hash] ??= []
this.messagesQueue[hash].push({
this.toWorkerMessagesQueue[hash] ??= []
this.toWorkerMessagesQueue[hash].push({
// this.workers[hash].postMessage({
type: 'dirty',
x: pos.x,
@ -767,11 +767,11 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
this.queueAwaited = true
setTimeout(() => {
// group messages and send as one
for (const workerIndex in this.messagesQueue) {
for (const workerIndex in this.toWorkerMessagesQueue) {
const worker = this.workers[Number(workerIndex)]
worker.postMessage(this.messagesQueue[workerIndex])
worker.postMessage(this.toWorkerMessagesQueue[workerIndex])
}
this.messagesQueue = {}
this.toWorkerMessagesQueue = {}
this.queueAwaited = false
})
}

View file

@ -124,7 +124,7 @@ export class PanoramaRenderer {
async worldBlocksPanorama () {
const version = '1.21.4'
this.options.resourcesManager.currentConfig = { version }
this.options.resourcesManager.currentConfig = { version, noInventoryGui: true, }
await this.options.resourcesManager.updateAssetsData({ })
if (this.abortController.signal.aborted) return
console.time('load panorama scene')

View file

@ -299,20 +299,6 @@ export class WorldRendererThree extends WorldRendererCommon {
// this.debugRecomputedDeletedObjects++
// }
// if (!this.initialChunksLoad && this.enableChunksLoadDelay) {
// const newPromise = new Promise(resolve => {
// if (this.droppedFpsPercentage > 0.5) {
// setTimeout(resolve, 1000 / 50 * this.droppedFpsPercentage)
// } else {
// setTimeout(resolve)
// }
// })
// this.promisesQueue.push(newPromise)
// for (const promise of this.promisesQueue) {
// await promise
// }
// }
const geometry = new THREE.BufferGeometry()
geometry.setAttribute('position', new THREE.BufferAttribute(data.geometry.positions, 3))
geometry.setAttribute('normal', new THREE.BufferAttribute(data.geometry.normals, 3))

View file

@ -702,6 +702,7 @@ export async function connect (connectOptions: ConnectOptions) {
}
})
})
await appViewer.resourcesManager.promiseAssetsReady
}
console.log('try to focus window')
window.focus?.()

View file

@ -119,12 +119,13 @@ export const onGameLoad = () => {
if (!appViewer.resourcesManager['_inventoryChangeTracked']) {
appViewer.resourcesManager['_inventoryChangeTracked'] = true
const upWindowItems = () => {
const texturesChanged = () => {
if (!lastWindow) return
upWindowItemsLocal()
upJei(lastJeiSearch)
}
appViewer.resourcesManager.on('assetsInventoryReady', () => upWindowItems())
appViewer.resourcesManager.on('assetsTexturesUpdated', () => upWindowItems())
appViewer.resourcesManager.on('assetsInventoryReady', () => texturesChanged())
appViewer.resourcesManager.on('assetsTexturesUpdated', () => texturesChanged())
}
}
@ -176,6 +177,7 @@ const getImage = ({ path = undefined as string | undefined, texture = undefined
export type ResolvedItemModelRender = {
modelName: string,
originalItemName?: string
}
export const renderSlot = (model: ResolvedItemModelRender, debugIsQuickbar = false, fullBlockModelSupport = false): {
@ -200,7 +202,8 @@ export const renderSlot = (model: ResolvedItemModelRender, debugIsQuickbar = fal
if (!fullBlockModelSupport) {
const atlas = activeGuiAtlas.atlas?.json
// todo atlas holds all rendered blocks, not all possibly rendered item/block models, need to request this on demand instead (this is how vanilla works)
const item = atlas?.textures[itemModelName.replace('minecraft:', '').replace('block/', '').replace('blocks/', '').replace('item/', '').replace('items/', '').replace('_inventory', '').replace('_bottom', '').replace('_height2', '').replace('_stable', '').replace('_unstable', '')]
const tryGetAtlasTexture = (name?: string) => name && atlas?.textures[name.replace('minecraft:', '').replace('block/', '').replace('blocks/', '').replace('item/', '').replace('items/', '').replace('_inventory', '')]
const item = tryGetAtlasTexture(itemModelName) ?? tryGetAtlasTexture(model.originalItemName)
if (item) {
const x = item.u * atlas.width
const y = item.v * atlas.height
@ -217,6 +220,7 @@ export const renderSlot = (model: ResolvedItemModelRender, debugIsQuickbar = fal
assertDefined(appViewer.resourcesManager.currentResources?.itemsRenderer)
itemTexture =
appViewer.resourcesManager.currentResources.itemsRenderer.getItemTexture(itemModelName, {}, false, fullBlockModelSupport)
?? (model.originalItemName ? appViewer.resourcesManager.currentResources.itemsRenderer.getItemTexture(model.originalItemName, {}, false, fullBlockModelSupport) : undefined)
?? appViewer.resourcesManager.currentResources.itemsRenderer.getItemTexture('item/missing_texture')!
} catch (err) {
inGameError(`Failed to render item ${itemModelName} (original: ${originalItemName}) on ${bot.version} (resourcepack: ${options.enabledResourcepack}): ${err.stack}`)
@ -249,20 +253,29 @@ const getItemName = (slot: Item | RenderItem | null) => {
return text.join('')
}
let lastMappedSots = [] as any[]
let lastMappedSlots = [] as any[]
const itemToVisualKey = (slot: RenderItem | Item | null) => {
if (!slot) return null
return slot.name + slot['count'] + (slot['metadata'] ?? '-') + (slot.nbt ? JSON.stringify(slot.nbt) : '') + (slot['components'] ? JSON.stringify(slot['components']) : '')
if (!slot) return ''
const keys = [
slot.name,
slot['count'],
slot['metadata'],
slot.nbt ? JSON.stringify(slot.nbt) : '',
slot['components'] ? JSON.stringify(slot['components']) : '',
activeGuiAtlas.version,
].join('|')
return keys
}
const mapSlots = (slots: Array<RenderItem | Item | null>, isJei = false) => {
const newSlots = slots.map((slot, i) => {
// todo stateid
if (!slot) return
if (!slot) return null
if (!isJei) {
const oldKey = itemToVisualKey(lastMappedSots[i])
if (oldKey && oldKey === itemToVisualKey(slot)) {
return lastMappedSots[i]
const oldKey = lastMappedSlots[i]?.cacheKey
const newKey = itemToVisualKey(slot)
slot['cacheKey'] = newKey
if (oldKey && oldKey === newKey) {
return lastMappedSlots[i]
}
}
@ -270,7 +283,7 @@ const mapSlots = (slots: Array<RenderItem | Item | null>, isJei = false) => {
if (slot.durabilityUsed && slot.maxDurability) slot.durabilityUsed = Math.min(slot.durabilityUsed, slot.maxDurability)
const debugIsQuickbar = !isJei && i === bot.inventory.hotbarStart + bot.quickBarSlot
const modelName = getItemModelName(slot, { 'minecraft:display_context': 'gui', }, appViewer.resourcesManager)
const slotCustomProps = renderSlot({ modelName }, debugIsQuickbar)
const slotCustomProps = renderSlot({ modelName, originalItemName: slot.name }, debugIsQuickbar)
const itemCustomName = getItemName(slot)
Object.assign(slot, { ...slotCustomProps, displayName: itemCustomName ?? slot.displayName })
//@ts-expect-error
@ -285,7 +298,7 @@ const mapSlots = (slots: Array<RenderItem | Item | null>, isJei = false) => {
}
return slot
})
lastMappedSots = newSlots
lastMappedSlots = newSlots
return newSlots
}
@ -331,7 +344,9 @@ const implementedContainersGuiMap = {
'minecraft:villager': 'VillagerWin',
}
let lastJeiSearch = ''
const upJei = (search: string) => {
lastJeiSearch = search
search = search.toLowerCase()
// todo fix pre flat
const itemsArray = [

View file

@ -51,6 +51,7 @@ export interface ResourcesCurrentConfig {
version: string
texturesVersion?: string
noBlockstatesModels?: boolean
noInventoryGui?: boolean
includeOnlyBlocks?: string[]
}
@ -58,6 +59,7 @@ export interface UpdateAssetsRequest {
_?: false
}
const STABLE_MODELS_VERSION = '1.21.4'
export class ResourcesManager extends (EventEmitter as new () => TypedEmitter<ResourceManagerEvents>) {
// Source data (imported, not changing)
sourceBlockStatesModels: any = null
@ -69,6 +71,10 @@ export class ResourcesManager extends (EventEmitter as new () => TypedEmitter<Re
currentResources: LoadedResources | undefined
currentConfig: ResourcesCurrentConfig | undefined
abortController = new AbortController()
_promiseAssetsReadyResolvers = Promise.withResolvers<void>()
get promiseAssetsReady () {
return this._promiseAssetsReadyResolvers.promise
}
async loadMcData (version: string) {
await loadMinecraftData(version)
@ -85,6 +91,7 @@ export class ResourcesManager extends (EventEmitter as new () => TypedEmitter<Re
async updateAssetsData (request: UpdateAssetsRequest, unstableSkipEvent = false) {
if (!this.currentConfig) throw new Error('No config loaded')
this._promiseAssetsReadyResolvers = Promise.withResolvers()
const abortController = new AbortController()
await this.loadSourceData(this.currentConfig.version)
if (abortController.signal.aborted) return
@ -165,7 +172,7 @@ export class ResourcesManager extends (EventEmitter as new () => TypedEmitter<Re
resources.worldBlockProvider = worldBlockProvider(
resources.blockstatesModels,
resources.blocksAtlasParser.atlas,
'latest'
STABLE_MODELS_VERSION
)
}
@ -174,8 +181,17 @@ export class ResourcesManager extends (EventEmitter as new () => TypedEmitter<Re
this.currentResources = resources
if (!unstableSkipEvent) { // todo rework resourcepack optimization
this.emit('assetsTexturesUpdated')
}
if (this.currentConfig.noInventoryGui) {
this._promiseAssetsReadyResolvers.resolve()
} else {
void this.generateGuiTextures().then(() => {
this.emit('assetsInventoryReady')
if (abortController.signal.aborted) return
if (!unstableSkipEvent) {
this.emit('assetsInventoryReady')
}
this._promiseAssetsReadyResolvers.resolve()
})
}
}