Merge remote-tracking branch 'origin/next' into light-engine

This commit is contained in:
Vitaly Turovsky 2025-04-12 04:06:53 +03:00
commit b1ba2cd470
14 changed files with 4875 additions and 5561 deletions

View file

@ -13,17 +13,17 @@ jobs:
(
github.event_name == 'issue_comment' &&
github.event.issue.pull_request != '' &&
contains(github.event.comment.body, '/benchmark')
(startsWith(github.event.comment.body, '/benchmark'))
)
permissions:
pull-requests: write
steps:
- run: lscpu
- name: Checkout
uses: actions/checkout@v2
# with:
# ref: refs/pull/${{ github.event.issue.number }}/head
- run: npm i -g pnpm@9.0.4
- name: Setup pnpm
uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 18
@ -33,8 +33,10 @@ jobs:
jq '.dependencies.cypress = .optionalDependencies.cypress | del(.optionalDependencies.cypress)' package.json > package.json.tmp
mv package.json.tmp package.json
- run: pnpm install --no-frozen-lockfile
- run: pnpm build
- run: nohup pnpm prod-start &
- run: pnpm test:benchmark
id: benchmark
continue-on-error: true
@ -49,6 +51,7 @@ jobs:
else
echo "BENCHMARK_RESULT=Benchmark failed to run or produce results" >> $GITHUB_ENV
fi
- uses: mshick/add-pr-comment@v2
with:
allow-repeats: true

View file

@ -9,8 +9,10 @@ After forking the repository, run the following commands to get started:
4. Let us know if you are working on something and be sure to open a PR if you got any changes. Happy coding!
*(1): If you are getting `Cannot find matching keyid` update corepack to the latest version with `npm i -g corepack`.
*(2): If still something doesn't work ensure you have the right nodejs version with `node -v` (tested on 22.x)
*(3): For GitHub codespaces (cloud ide): Run `pnpm i @rsbuild/core@1.2.4 @rsbuild/plugin-node-polyfill@1.3.0 @rsbuild/plugin-react@1.1.0 @rsbuild/plugin-typed-css-modules@1.0.2` command to avoid crashes because of limited ram
<!-- *(3): For GitHub codespaces (cloud ide): Run `pnpm i @rsbuild/core@1.2.4 @rsbuild/plugin-node-polyfill@1.3.0 @rsbuild/plugin-react@1.1.0 @rsbuild/plugin-typed-css-modules@1.0.2` command to avoid crashes because of limited ram -->
## Project Structure

View file

@ -4,8 +4,8 @@ FROM node:18-alpine AS build
RUN apk add git
WORKDIR /app
COPY . /app
# install pnpm
RUN npm i -g pnpm@9.0.4
# install pnpm with corepack
RUN corepack enable
# Build arguments
ARG DOWNLOAD_SOUNDS=false
ARG DISABLE_SERVICE_WORKER=false
@ -35,7 +35,7 @@ WORKDIR /app
COPY --from=build /app/dist /app/dist
COPY server.js /app/server.js
# Install express
RUN npm i -g pnpm@9.0.4
RUN npm i -g pnpm@10.8.0
RUN npm init -yp
RUN pnpm i express github:zardoy/prismarinejs-net-browserify compression cors
EXPOSE 8080

View file

@ -76,18 +76,18 @@
"esbuild-plugin-polyfill-node": "^0.3.0",
"express": "^4.18.2",
"filesize": "^10.0.12",
"flying-squid": "npm:@zardoy/flying-squid@^0.0.58",
"flying-squid": "npm:@zardoy/flying-squid@^0.0.59",
"fs-extra": "^11.1.1",
"google-drive-browserfs": "github:zardoy/browserfs#google-drive",
"jszip": "^3.10.1",
"lodash-es": "^4.17.21",
"mcraft-fun-mineflayer": "^0.1.14",
"minecraft-data": "3.83.1",
"minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master",
"mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader",
"mojangson": "^2.0.4",
"net-browserify": "github:zardoy/prismarinejs-net-browserify",
"node-gzip": "^1.1.2",
"mcraft-fun-mineflayer": "^0.1.14",
"peerjs": "^1.5.0",
"pixelarticons": "^1.8.1",
"pretty-bytes": "^6.1.1",
@ -119,11 +119,11 @@
"workbox-build": "^7.0.0"
},
"devDependencies": {
"@rsbuild/core": "1.0.1-beta.9",
"@rsbuild/plugin-node-polyfill": "1.0.3",
"@rsbuild/plugin-react": "1.0.1-beta.9",
"@rsbuild/plugin-type-check": "1.0.1-beta.9",
"@rsbuild/plugin-typed-css-modules": "1.0.1",
"@rsbuild/core": "1.3.5",
"@rsbuild/plugin-node-polyfill": "1.3.0",
"@rsbuild/plugin-react": "1.2.0",
"@rsbuild/plugin-type-check": "1.2.1",
"@rsbuild/plugin-typed-css-modules": "1.0.2",
"@storybook/addon-essentials": "^7.4.6",
"@storybook/addon-links": "^7.4.6",
"@storybook/blocks": "^7.4.6",
@ -152,16 +152,16 @@
"http-server": "^14.1.1",
"https-browserify": "^1.0.0",
"mc-assets": "^0.2.52",
"mineflayer-mouse": "^0.1.7",
"minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next",
"mineflayer": "github:GenerelSchwerz/mineflayer",
"mineflayer-mouse": "^0.1.7",
"mineflayer-pathfinder": "^2.4.4",
"npm-run-all": "^4.1.5",
"os-browserify": "^0.3.0",
"path-browserify": "^1.0.1",
"path-exists-cli": "^2.0.0",
"renderer": "link:renderer",
"process": "github:PrismarineJS/node-process",
"renderer": "link:renderer",
"rimraf": "^5.0.1",
"storybook": "^7.4.6",
"stream-browserify": "^3.0.0",
@ -215,11 +215,21 @@
"ignoreDependencies": []
},
"patchedDependencies": {
"three@0.154.0": "patches/three@0.154.0.patch",
"pixelarticons@1.8.1": "patches/pixelarticons@1.8.1.patch",
"mineflayer-item-map-downloader@1.2.0": "patches/mineflayer-item-map-downloader@1.2.0.patch",
"minecraft-protocol@1.54.0": "patches/minecraft-protocol@1.54.0.patch"
}
"minecraft-protocol": "patches/minecraft-protocol.patch"
},
"ignoredBuiltDependencies": [
"canvas",
"core-js",
"gl"
],
"onlyBuiltDependencies": [
"sharp",
"cypress",
"esbuild",
"fsevents"
]
},
"packageManager": "pnpm@9.0.4"
"packageManager": "pnpm@10.8.0+sha512.0e82714d1b5b43c74610193cb20734897c1d00de89d0e18420aebc5977fa13d780a9cb05734624e81ebd81cc876cd464794850641c48b9544326b5622ca29971"
}

View file

@ -1,16 +0,0 @@
diff --git a/examples/jsm/webxr/VRButton.js b/examples/jsm/webxr/VRButton.js
index 6856a21b17aa45d7922bbf776fd2d7e63c7a9b4e..0925b706f7629bd52f0bb5af469536af8f5fce2c 100644
--- a/examples/jsm/webxr/VRButton.js
+++ b/examples/jsm/webxr/VRButton.js
@@ -62,7 +62,10 @@ class VRButton {
// ('local' is always available for immersive sessions and doesn't need to
// be requested separately.)
- const sessionInit = { optionalFeatures: [ 'local-floor', 'bounded-floor', 'hand-tracking', 'layers' ] };
+ const sessionInit = {
+ optionalFeatures: ['local-floor', 'bounded-floor', 'layers'],
+ domOverlay: { root: document.body },
+ };
navigator.xr.requestSession( 'immersive-vr', sessionInit ).then( onSessionStarted );
} else {

10615
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,7 @@ import { defineConfig, ModifyRspackConfigUtils } from '@rsbuild/core';
import { pluginNodePolyfill } from '@rsbuild/plugin-node-polyfill';
import { pluginReact } from '@rsbuild/plugin-react';
import path from 'path'
import fs from 'fs'
export const appAndRendererSharedConfig = () => defineConfig({
dev: {
@ -60,6 +61,12 @@ export const appAndRendererSharedConfig = () => defineConfig({
],
tools: {
rspack (config, helpers) {
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf8'))
const hasFileProtocol = Object.values(packageJson.pnpm.overrides).some((dep) => (dep as string).startsWith('file:'))
if (hasFileProtocol) {
// enable node_modules watching
config.watchOptions.ignored = /\.git/
}
rspackViewerConfig(config, helpers)
}
},

View file

@ -26,7 +26,7 @@ export const addNewStat = (id: string, width = 80, x = rightOffset, y = lastY) =
return {
updateText (text: string) {
if (pane.innerText === text) return
if (pane.innerText === text || pane.style.display === 'none') return
pane.innerText = text
},
setVisibility (visible: boolean) {

View file

@ -11,8 +11,8 @@ import { delayedIterator } from '../../playground/shared'
import { chunkPos } from './simpleUtils'
import { processLightChunk, updateBlockLight } from './lightEngine'
export type ChunkPosKey = string
type ChunkPos = { x: number, z: number }
export type ChunkPosKey = string // like '16,16'
type ChunkPos = { x: number, z: number } // like { x: 16, z: 16 }
export type WorldDataEmitterEvents = {
chunkPosUpdate: (data: { pos: Vec3 }) => void
@ -40,6 +40,9 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
private readonly lastPos: Vec3
private eventListeners: Record<string, any> = {}
private readonly emitter: WorldDataEmitter
waitingSpiralChunksLoad = {} as Record<ChunkPosKey, (value: boolean) => void>
addWaitTime = 1
/* config */ keepChunksDistance = 0
/* config */ isPlayground = false
@ -64,7 +67,7 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
if (val) throw new Error('setBlockStateId returned promise (not supported)')
const chunkX = Math.floor(position.x / 16)
const chunkZ = Math.floor(position.z / 16)
if (!this.loadedChunks[`${chunkX},${chunkZ}`]) {
if (!this.loadedChunks[`${chunkX},${chunkZ}`] && !this.waitingSpiralChunksLoad[`${chunkX},${chunkZ}`]) {
void this.loadChunk({ x: chunkX, z: chunkZ })
return
}
@ -111,7 +114,10 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
this.emitter.emit('entity', { id: e.id, delete: true })
},
chunkColumnLoad: (pos: Vec3) => {
void this.loadChunk(pos)
if (this.waitingSpiralChunksLoad[`${pos.x},${pos.z}`]) {
this.waitingSpiralChunksLoad[`${pos.x},${pos.z}`](true)
delete this.waitingSpiralChunksLoad[`${pos.x},${pos.z}`]
}
},
chunkColumnUnload: (pos: Vec3) => {
this.unloadChunk(pos)
@ -134,7 +140,9 @@ 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)
void this.loadChunk(chunkPos, true)
if (!this.waitingSpiralChunksLoad[`${chunkX},${chunkZ}`]) {
void this.loadChunk(chunkPos, true)
}
})
this.emitter.on('listening', () => {
@ -188,9 +196,27 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
}
async _loadChunks (positions: Vec3[], sliceSize = 5) {
// stop loading previous chunks
for (const pos of Object.keys(this.waitingSpiralChunksLoad)) {
this.waitingSpiralChunksLoad[pos](false)
delete this.waitingSpiralChunksLoad[pos]
}
const promises = [] as Array<Promise<void>>
await delayedIterator(positions, this.addWaitTime, (pos) => {
promises.push(this.loadChunk(pos))
let continueLoading = true
await delayedIterator(positions, this.addWaitTime, async (pos) => {
const promise = (async () => {
if (!continueLoading || this.loadedChunks[`${pos.x},${pos.z}`]) return
if (!this.world.getColumnAt(pos)) {
continueLoading = await new Promise<boolean>(resolve => {
this.waitingSpiralChunksLoad[`${pos.x},${pos.z}`] = resolve
})
}
if (!continueLoading) return
await this.loadChunk(pos)
})()
promises.push(promise)
})
await Promise.all(promises)
}
@ -271,6 +297,8 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
const [botX, botZ] = chunkPos(pos)
if (lastX !== botX || lastZ !== botZ || force) {
this.emitter.emit('chunkPosUpdate', { pos })
// unload chunks that are no longer in view
const newViewToUnload = new ViewRect(botX, botZ, this.viewDistance + this.keepChunksDistance)
const chunksToUnload: Vec3[] = []
for (const coords of Object.keys(this.loadedChunks)) {
@ -286,6 +314,8 @@ export class WorldDataEmitter extends (EventEmitter as new () => TypedEmitter<Wo
for (const p of chunksToUnload) {
this.unloadChunk(p)
}
// load new chunks
const positions = generateSpiralMatrix(this.viewDistance).map(([x, z]) => {
const pos = new Vec3((botX + x) * 16, 0, (botZ + z) * 16)
if (!this.loadedChunks[`${pos.x},${pos.z}`]) return pos

View file

@ -16,6 +16,8 @@ interface MediaProperties {
loop?: boolean
volume?: number
autoPlay?: boolean
allowLighting?: boolean
}
export class ThreeJsMedia {
@ -212,7 +214,8 @@ export class ThreeJsMedia {
const geometry = new THREE.PlaneGeometry(1, 1)
// Create material with initial properties using background texture
const material = new THREE.MeshLambertMaterial({
const MaterialClass = props.allowLighting ? THREE.MeshLambertMaterial : THREE.MeshBasicMaterial
const material = new MaterialClass({
map: backgroundTexture,
transparent: true,
side: props.doubleSide ? THREE.DoubleSide : THREE.FrontSide,

View file

@ -34,6 +34,7 @@ const fixtures: Record<string, BenchmarkFixture> = {
Error.stackTraceLimit = Error.stackTraceLimit < 30 ? 30 : Error.stackTraceLimit
const SESSION_STORAGE_BACKUP_KEY = 'benchmark-backup'
export const openBenchmark = async (renderDistance = DEFAULT_RENDER_DISTANCE) => {
let fixtureNameOpen = appQueryParams.openBenchmark
if (!fixtureNameOpen || fixtureNameOpen === '1' || fixtureNameOpen === 'true' || fixtureNameOpen === 'zip') {
@ -41,7 +42,6 @@ export const openBenchmark = async (renderDistance = DEFAULT_RENDER_DISTANCE) =>
}
const SESSION_STORAGE_BACKUP_KEY = 'benchmark-backup'
if (sessionStorage.getItem(SESSION_STORAGE_BACKUP_KEY)) {
const backup = JSON.stringify(JSON.parse(sessionStorage.getItem(SESSION_STORAGE_BACKUP_KEY)!), null, 2)
setLoadingScreenStatus('Either other tab with benchmark is open or page crashed. Last data backup is downloaded. Reload page to retry.')
@ -103,6 +103,7 @@ export const openBenchmark = async (renderDistance = DEFAULT_RENDER_DISTANCE) =>
if (fixture.spawn) {
fixtureName += ` - ${fixture.spawn.join(' ')}`
}
fixtureName += ` - ${renderDistance}`
if (process.env.NODE_ENV !== 'development') { // do not delay
setLoadingScreenStatus('Benchmark requested... Getting screen refresh rate')
@ -212,6 +213,7 @@ export const openBenchmark = async (renderDistance = DEFAULT_RENDER_DISTANCE) =>
},
}
})
document.addEventListener('cypress-world-ready', () => {
clearInterval(saveBackupInterval)
sessionStorage.removeItem(SESSION_STORAGE_BACKUP_KEY)
@ -271,6 +273,12 @@ export const openBenchmark = async (renderDistance = DEFAULT_RENDER_DISTANCE) =>
})
}
// add before unload
window.addEventListener('beforeunload', () => {
// remove sessionStorage backup
sessionStorage.removeItem(SESSION_STORAGE_BACKUP_KEY)
})
document.addEventListener('pointerlockchange', (e) => {
const panel = document.querySelector<HTMLDivElement>('#benchmark-panel')
if (panel) {

View file

@ -89,7 +89,10 @@ export const formatMessage = (message: MessageInput, mcData: IndexedData = globa
}
if (msg.extra) {
for (const ex of msg.extra) {
for (let ex of msg.extra) {
if (typeof ex === 'string') {
ex = { text: ex }
}
readMsg({ ...styles, ...ex })
}
}

View file

@ -103,6 +103,7 @@ export async function getScreenRefreshRate (): Promise<number> {
window.setTimeout(() => {
window.cancelAnimationFrame(requestId!)
requestId = null
resolve(0)
}, 500)
return new Promise(_resolve => {