diff --git a/renderer/viewer/lib/worldDataEmitter.ts b/renderer/viewer/lib/worldDataEmitter.ts index c9329d01..5e28c007 100644 --- a/renderer/viewer/lib/worldDataEmitter.ts +++ b/renderer/viewer/lib/worldDataEmitter.ts @@ -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) { - private loadedChunks: Record - private readonly lastPos: Vec3 + loadedChunks: Record + readonly lastPos: Vec3 private eventListeners: Record = {} private readonly emitter: WorldDataEmitter @@ -140,7 +140,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter { 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 = { + '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(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>((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 ( +
+
+ {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 ( +
{ + 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')} +
+ ) + })} +
+ + {showSidebar && selectedChunk && ( +
+ {selectedChunk.displayLines.map((line, i) => ( +
+ {line} +
+ ))} +
+
Sidebar Info:
+ {selectedChunk.sidebarLines.map((line, i) => ( +
{line}
+ ))} +
+
+ )} +
+ ) +} diff --git a/src/react/ChunksDebugScreen.tsx b/src/react/ChunksDebugScreen.tsx new file mode 100644 index 00000000..c65eee16 --- /dev/null +++ b/src/react/ChunksDebugScreen.tsx @@ -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 + + +} + +export default () => { + const isActive = useIsModalActive('chunks-debug') + if (!isActive) return null + + return +} diff --git a/src/react/DebugOverlay.tsx b/src/react/DebugOverlay.tsx index af0d6675..610bcea9 100644 --- a/src/react/DebugOverlay.tsx +++ b/src/react/DebugOverlay.tsx @@ -180,6 +180,7 @@ export default () => {

XYZ: {pos.x.toFixed(3)} / {pos.y.toFixed(3)} / {pos.z.toFixed(3)}

Chunk: {Math.floor(pos.x % 16)} ~ {Math.floor(pos.z % 16)} in {Math.floor(pos.x / 16)} ~ {Math.floor(pos.z / 16)}

+

Section: {Math.floor(pos.x / 16) * 16}, {Math.floor(pos.y / 16) * 16}, {Math.floor(pos.z / 16) * 16}

Packets: {packetsString}

Client TPS: {clientTps}

Facing (viewer): {bot.entity.yaw.toFixed(3)} {bot.entity.pitch.toFixed(3)}

diff --git a/src/reactUi.tsx b/src/reactUi.tsx index bbfbc2ee..93db7706 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -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') && } {!disabledUiParts.includes('books') && } {!disabledUiParts.includes('bossbars') && displayBossBars && } +