feat: Add F3+H chunks debug screen! not really useful for now since chunks not visible bug was not fixed yet

This commit is contained in:
Vitaly Turovsky 2025-04-16 18:24:19 +03:00
commit 73ccb48d02
6 changed files with 246 additions and 3 deletions

View file

@ -37,8 +37,8 @@ export type WorldDataEmitterEvents = {
* It's up to the consumer to serialize the data if needed
*/
export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<WorldDataEmitterEvents>) {
private loadedChunks: Record<ChunkPosKey, boolean>
private readonly lastPos: Vec3
loadedChunks: Record<ChunkPosKey, boolean>
readonly lastPos: Vec3
private eventListeners: Record<string, any> = {}
private readonly emitter: WorldDataEmitter
@ -140,7 +140,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
bot._client.on('update_light', ({ chunkX, chunkZ }) => {
const chunkPos = new Vec3(chunkX * 16, 0, chunkZ * 16)
if (!this.waitingSpiralChunksLoad[`${chunkX},${chunkZ}`]) {
if (!this.waitingSpiralChunksLoad[`${chunkX},${chunkZ}`] && this.loadedChunks[`${chunkX},${chunkZ}`]) {
void this.loadChunk(chunkPos, true)
}
})
@ -242,6 +242,8 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
async loadChunk (pos: ChunkPos, isLightUpdate = false) {
const [botX, botZ] = chunkPos(this.lastPos)
console.log('loadChunk', botX - pos.x / 16, botZ - pos.z / 16)
const dx = Math.abs(botX - Math.floor(pos.x / 16))
const dz = Math.abs(botZ - Math.floor(pos.z / 16))
if (dx <= this.viewDistance && dz <= this.viewDistance) {

View file

@ -614,6 +614,13 @@ export const f3Keybinds: Array<{
},
mobileTitle: 'Toggle chunk borders',
},
{
key: 'KeyH',
action () {
showModal({ reactType: 'chunks-debug' })
},
mobileTitle: 'Show Chunks Debug',
},
{
key: 'KeyY',
async action () {

130
src/react/ChunksDebug.tsx Normal file
View file

@ -0,0 +1,130 @@
import { useEffect, useRef, useState } from 'react'
import './LoadingChunks.css'
export interface ChunkDebug {
x: number // like -32
z: number // like -32
lines: string[]
sidebarLines: string[]
state: 'server-waiting' | 'order-queued' | 'client-waiting' | 'client-processing' | 'done-empty' | 'done'
}
interface ProcessedChunk extends ChunkDebug {
relX: number
relZ: number
displayLines: string[]
}
const stateColors: Record<ChunkDebug['state'], string> = {
'server-waiting': 'gray',
'order-queued': 'darkorange',
'client-waiting': 'yellow',
'client-processing': 'yellow',
'done-empty': 'darkgreen',
'done': 'limegreen',
}
export default ({
chunks,
playerChunk,
maxDistance,
tileSize = 16,
fontSize = 5,
}: {
chunks: ChunkDebug[]
playerChunk: { x: number, z: number }
maxDistance: number,
tileSize?: number
fontSize?: number
}) => {
const [selectedChunk, setSelectedChunk] = useState<ProcessedChunk | null>(null)
const [showSidebar, setShowSidebar] = useState(false)
// Calculate grid dimensions based on maxDistance
const gridSize = maxDistance * 2 + 1
const centerIndex = maxDistance
// Process chunks to get only the last one for each position and within maxDistance
const processedChunks = chunks.reduce<Record<string, ProcessedChunk>>((acc, chunk) => {
const relX = Math.floor((chunk.x - playerChunk.x) / 16)
const relZ = Math.floor((chunk.z - playerChunk.z) / 16)
// Skip chunks outside maxDistance
if (Math.abs(relX) > maxDistance || Math.abs(relZ) > maxDistance) return acc
const key = `${chunk.x},${chunk.z}`
acc[key] = {
...chunk,
relX,
relZ,
displayLines: [`${relX},${relZ} (${chunk.x},${chunk.z})`, ...chunk.lines]
}
return acc
}, {})
return (
<div style={{ display: 'flex', gap: '10px' }}>
<div style={{
display: 'grid',
gridTemplateColumns: `repeat(${gridSize}, 1fr)`,
gridTemplateRows: `repeat(${gridSize}, 1fr)`,
gap: 1,
// width: `${tileSize * gridSize}px`,
// height: `${tileSize * gridSize}px`,
}}>
{Array.from({ length: gridSize * gridSize }).map((_, i) => {
const relX = -maxDistance + (i % gridSize)
const relZ = -maxDistance + Math.floor(i / gridSize)
const x = playerChunk.x + relX * 16
const z = playerChunk.z + relZ * 16
const chunk = processedChunks[`${x},${z}`]
return (
<div
key={`${x},${z}`}
onClick={() => {
if (chunk) {
setSelectedChunk(chunk)
setShowSidebar(true)
}
}}
style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
background: chunk ? stateColors[chunk.state] : 'black',
color: 'white',
fontSize: `${fontSize}px`,
cursor: chunk ? 'pointer' : 'default',
position: 'relative',
width: `${tileSize}px`,
height: `${tileSize}px`,
// pre-wrap
whiteSpace: 'pre',
}}
>
{relX}, {relZ}{'\n'}
{chunk?.lines.join('\n')}
</div>
)
})}
</div>
{showSidebar && selectedChunk && (
<div style={{ display: 'flex', flexDirection: 'column', gap: '5px' }}>
{selectedChunk.displayLines.map((line, i) => (
<div key={i} style={{ fontSize: '10px', wordBreak: 'break-word' }}>
{line}
</div>
))}
<div style={{ marginTop: '10px', fontSize: '10px' }} className='text-select'>
<div>Sidebar Info:</div>
{selectedChunk.sidebarLines.map((line, i) => (
<div key={i}>{line}</div>
))}
</div>
</div>
)}
</div>
)
}

View file

@ -0,0 +1,100 @@
import { useEffect, useState } from 'react'
import { useUtilsEffect } from '@zardoy/react-util'
import { WorldRendererCommon } from 'renderer/viewer/lib/worldrendererCommon'
import { WorldRendererThree } from 'renderer/viewer/three/worldrendererThree'
import Screen from './Screen'
import ChunksDebug, { ChunkDebug } from './ChunksDebug'
import { useIsModalActive } from './utilsApp'
const Inner = () => {
const [playerX, setPlayerX] = useState(Math.floor(worldView!.lastPos.x / 16) * 16)
const [playerZ, setPlayerZ] = useState(Math.floor(worldView!.lastPos.z / 16) * 16)
const [update, setUpdate] = useState(0)
useUtilsEffect(({ interval }) => {
interval(
500,
() => {
setPlayerX(Math.floor(worldView!.lastPos.x / 16) * 16)
setPlayerZ(Math.floor(worldView!.lastPos.z / 16) * 16)
setUpdate(u => u + 1)
}
)
}, [])
const chunksWaitingServer = Object.keys(worldView!.waitingSpiralChunksLoad).map((x): ChunkDebug => ({
x: Number(x.split(',')[0]),
z: Number(x.split(',')[1]),
state: 'server-waiting',
lines: [],
sidebarLines: [],
}))
const world = globalThis.world as WorldRendererThree
const loadedSectionsChunks = Object.fromEntries(Object.keys(world.sectionObjects).map(sectionPos => {
const [x, y, z] = sectionPos.split(',').map(Number)
return [`${x},${z}`, true]
}))
const chunksWaitingClient = Object.keys(worldView!.loadedChunks).map((x): ChunkDebug => ({
x: Number(x.split(',')[0]),
z: Number(x.split(',')[1]),
state: 'client-waiting',
lines: [],
sidebarLines: [],
}))
const clientProcessingChunks = Object.keys(world.loadedChunks).map((x): ChunkDebug => ({
x: Number(x.split(',')[0]),
z: Number(x.split(',')[1]),
state: 'client-processing',
lines: [],
sidebarLines: [],
}))
const chunksDoneEmpty = Object.keys(world.finishedChunks)
.filter(chunkPos => !loadedSectionsChunks[chunkPos])
.map((x): ChunkDebug => ({
x: Number(x.split(',')[0]),
z: Number(x.split(',')[1]),
state: 'done-empty',
lines: [],
sidebarLines: [],
}))
const chunksDone = Object.keys(world.finishedChunks).map((x): ChunkDebug => ({
x: Number(x.split(',')[0]),
z: Number(x.split(',')[1]),
state: 'done',
lines: [],
sidebarLines: [],
}))
const allChunks = [
...chunksWaitingServer,
...chunksWaitingClient,
...clientProcessingChunks,
...chunksDone,
...chunksDoneEmpty,
]
return <Screen title="Chunks Debug">
<ChunksDebug
chunks={allChunks}
playerChunk={{
x: playerX,
z: playerZ
}}
maxDistance={worldView!.viewDistance}
tileSize={32}
fontSize={8}
/>
</Screen>
}
export default () => {
const isActive = useIsModalActive('chunks-debug')
if (!isActive) return null
return <Inner />
}

View file

@ -180,6 +180,7 @@ export default () => {
<div className={styles.empty} />
<p>XYZ: {pos.x.toFixed(3)} / {pos.y.toFixed(3)} / {pos.z.toFixed(3)}</p>
<p>Chunk: {Math.floor(pos.x % 16)} ~ {Math.floor(pos.z % 16)} in {Math.floor(pos.x / 16)} ~ {Math.floor(pos.z / 16)}</p>
<p>Section: {Math.floor(pos.x / 16) * 16}, {Math.floor(pos.y / 16) * 16}, {Math.floor(pos.z / 16) * 16}</p>
<p>Packets: {packetsString}</p>
<p>Client TPS: {clientTps}</p>
<p>Facing (viewer): {bot.entity.yaw.toFixed(3)} {bot.entity.pitch.toFixed(3)}</p>

View file

@ -55,6 +55,8 @@ import PacketsReplayProvider from './react/PacketsReplayProvider'
import TouchInteractionHint from './react/TouchInteractionHint'
import { ua } from './react/utils'
import ControDebug from './react/ControDebug'
import ChunksDebug from './react/ChunksDebug'
import ChunksDebugScreen from './react/ChunksDebugScreen'
const isFirefox = ua.getBrowser().name === 'Firefox'
if (isFirefox) {
@ -157,6 +159,7 @@ const InGameUi = () => {
{!disabledUiParts.includes('crosshair') && <Crosshair />}
{!disabledUiParts.includes('books') && <BookProvider />}
{!disabledUiParts.includes('bossbars') && displayBossBars && <BossBarOverlayProvider />}
<ChunksDebugScreen />
</PerComponentErrorBoundary>
</div>