feat: Performance benchmark!! (#153)
This commit is contained in:
parent
ce5ef7c7cb
commit
0aa4d11bdd
20 changed files with 538 additions and 54 deletions
50
.github/workflows/benchmark.yml
vendored
Normal file
50
.github/workflows/benchmark.yml
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
name: Benchmark
|
||||
on:
|
||||
issue_comment:
|
||||
types: [created]
|
||||
push:
|
||||
branches:
|
||||
- perf-test
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
# if: >-
|
||||
# github.event.issue.pull_request != '' &&
|
||||
# (
|
||||
# contains(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
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
- run: pnpm install
|
||||
- run: pnpm build
|
||||
- run: nohup pnpm prod-start &
|
||||
- run: pnpm test:benchmark
|
||||
id: benchmark
|
||||
continue-on-error: true
|
||||
# read benchmark results from stdout
|
||||
- run: |
|
||||
if [ -f benchmark.txt ]; then
|
||||
# Format the benchmark results for GitHub comment
|
||||
BENCHMARK_RESULT=$(cat benchmark.txt | sed 's/^/- /')
|
||||
echo "BENCHMARK_RESULT<<EOF" >> $GITHUB_ENV
|
||||
echo "$BENCHMARK_RESULT" >> $GITHUB_ENV
|
||||
echo "EOF" >> $GITHUB_ENV
|
||||
else
|
||||
echo "BENCHMARK_RESULT=Benchmark failed to run or produce results" >> $GITHUB_ENV
|
||||
fi
|
||||
- uses: mshick/add-pr-comment@v2
|
||||
with:
|
||||
allow-repeats: true
|
||||
message: |
|
||||
Benchmark result: ${{ env.BENCHMARK_RESULT }}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
import { defineConfig } from 'cypress'
|
||||
|
||||
const isPerformanceTest = process.env.PERFORMANCE_TEST === 'true'
|
||||
|
||||
export default defineConfig({
|
||||
video: false,
|
||||
chromeWebSecurity: false,
|
||||
|
|
@ -32,7 +34,7 @@ export default defineConfig({
|
|||
return require('./cypress/plugins/index.js')(on, config)
|
||||
},
|
||||
baseUrl: 'http://localhost:8080',
|
||||
specPattern: 'cypress/e2e/**/*.spec.ts',
|
||||
specPattern: !isPerformanceTest ? 'cypress/e2e/smoke.spec.ts' : 'cypress/e2e/rendering_performance.spec.ts',
|
||||
excludeSpecPattern: ['**/__snapshots__/*', '**/__image_snapshots__/*'],
|
||||
},
|
||||
})
|
||||
|
|
|
|||
32
cypress/e2e/rendering_performance.spec.ts
Normal file
32
cypress/e2e/rendering_performance.spec.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/// <reference types="cypress" />
|
||||
import { BenchmarkAdapterInfo, getAllInfoLines } from '../../src/benchmarkAdapter'
|
||||
import { cleanVisit } from './shared'
|
||||
|
||||
it('Benchmark rendering performance', () => {
|
||||
cleanVisit('/?openBenchmark=true&renderDistance=5')
|
||||
// wait for render end event
|
||||
return cy.document().then({ timeout: 180_000 }, doc => {
|
||||
return new Cypress.Promise(resolve => {
|
||||
cy.log('Waiting for world to load')
|
||||
doc.addEventListener('cypress-world-ready', resolve)
|
||||
}).then(() => {
|
||||
cy.log('World loaded')
|
||||
})
|
||||
}).then(() => {
|
||||
cy.window().then(win => {
|
||||
const adapter = win.benchmarkAdapter as BenchmarkAdapterInfo
|
||||
|
||||
const messages = getAllInfoLines(adapter)
|
||||
// wait for 10 seconds
|
||||
cy.wait(10_000)
|
||||
const messages2 = getAllInfoLines(adapter, true)
|
||||
for (const message of messages) {
|
||||
cy.log(message)
|
||||
}
|
||||
for (const message of messages2) {
|
||||
cy.log(message)
|
||||
}
|
||||
cy.writeFile('benchmark.txt', [...messages, ...messages2].join('\n'))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
"prepare-project": "tsx scripts/genShims.ts && tsx scripts/makeOptimizedMcData.mjs && tsx scripts/genLargeDataAliases.ts",
|
||||
"check-build": "pnpm prepare-project && tsc && pnpm build",
|
||||
"test:cypress": "cypress run",
|
||||
"test:benchmark": "PERFORMANCE_TEST=true cypress run",
|
||||
"test:cypress:open": "cypress open",
|
||||
"test-unit": "vitest",
|
||||
"test:e2e": "start-test http-get://localhost:8080 test:cypress",
|
||||
|
|
@ -75,7 +76,7 @@
|
|||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"express": "^4.18.2",
|
||||
"filesize": "^10.0.12",
|
||||
"flying-squid": "npm:@zardoy/flying-squid@^0.0.51",
|
||||
"flying-squid": "npm:@zardoy/flying-squid@^0.0.58",
|
||||
"fs-extra": "^11.1.1",
|
||||
"google-drive-browserfs": "github:zardoy/browserfs#google-drive",
|
||||
"jszip": "^3.10.1",
|
||||
|
|
|
|||
64
pnpm-lock.yaml
generated
64
pnpm-lock.yaml
generated
|
|
@ -120,8 +120,8 @@ importers:
|
|||
specifier: ^10.0.12
|
||||
version: 10.0.12
|
||||
flying-squid:
|
||||
specifier: npm:@zardoy/flying-squid@^0.0.51
|
||||
version: '@zardoy/flying-squid@0.0.51(encoding@0.1.13)'
|
||||
specifier: npm:@zardoy/flying-squid@^0.0.58
|
||||
version: '@zardoy/flying-squid@0.0.58(encoding@0.1.13)'
|
||||
fs-extra:
|
||||
specifier: ^11.1.1
|
||||
version: 11.1.1
|
||||
|
|
@ -438,7 +438,7 @@ importers:
|
|||
version: 1.3.6
|
||||
prismarine-block:
|
||||
specifier: github:zardoy/prismarine-block#next-era
|
||||
version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
||||
version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0)
|
||||
prismarine-chunk:
|
||||
specifier: github:zardoy/prismarine-chunk#master
|
||||
version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1)
|
||||
|
|
@ -3541,8 +3541,8 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
|
||||
'@zardoy/flying-squid@0.0.51':
|
||||
resolution: {integrity: sha512-HHZ79H9NkS44lL9vk6gVEuJDJqj88gpiBt9Ihh5p4rHXTVbRid95riiNK5dD0kHI94P5/DXdtNalvmJDPU86oQ==}
|
||||
'@zardoy/flying-squid@0.0.58':
|
||||
resolution: {integrity: sha512-qkSoaYRpVQaAvcVgZDTe0i4PxaK2l2B6i7GfRCEsyYFl3UaNQYBwwocXqLrIwhsc63bwXa0XQe8UNUubz+A4eA==}
|
||||
engines: {node: '>=8'}
|
||||
hasBin: true
|
||||
|
||||
|
|
@ -6926,6 +6926,11 @@ packages:
|
|||
version: 1.54.0
|
||||
engines: {node: '>=22'}
|
||||
|
||||
minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284:
|
||||
resolution: {tarball: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284}
|
||||
version: 1.57.0
|
||||
engines: {node: '>=22'}
|
||||
|
||||
minecraft-wrap@1.5.1:
|
||||
resolution: {integrity: sha512-7DZ2WhrcRD3fUMau84l9Va0KWzV92SHNdB7mnNdNhgXID2aW6pjWuYPZi8MepEBemA4XKKdnDx7HmhTbkoiR8A==}
|
||||
hasBin: true
|
||||
|
|
@ -13652,7 +13657,7 @@ snapshots:
|
|||
- encoding
|
||||
- supports-color
|
||||
|
||||
'@zardoy/flying-squid@0.0.51(encoding@0.1.13)':
|
||||
'@zardoy/flying-squid@0.0.58(encoding@0.1.13)':
|
||||
dependencies:
|
||||
'@tootallnate/once': 2.0.0
|
||||
chalk: 5.3.0
|
||||
|
|
@ -13663,14 +13668,14 @@ snapshots:
|
|||
flatmap: 0.0.3
|
||||
long: 5.2.3
|
||||
minecraft-data: 3.83.1
|
||||
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/5ec3dd4b367fcc039fbcb3edd214fe3cf8178a6d(patch_hash=dkeyukcqlupmk563gwxsmjr3yu)(encoding@0.1.13)
|
||||
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(encoding@0.1.13)
|
||||
mkdirp: 2.1.6
|
||||
node-gzip: 1.1.2
|
||||
node-rsa: 1.1.1
|
||||
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1)
|
||||
prismarine-entity: 2.3.1
|
||||
prismarine-item: 1.16.0
|
||||
prismarine-nbt: 2.5.0
|
||||
prismarine-nbt: 2.7.0
|
||||
prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.83.1)
|
||||
prismarine-windows: 2.9.0
|
||||
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
||||
|
|
@ -17981,6 +17986,31 @@ snapshots:
|
|||
- encoding
|
||||
- supports-color
|
||||
|
||||
minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/9e116c3dd4682b17c4e2c80249a2447a093d9284(encoding@0.1.13):
|
||||
dependencies:
|
||||
'@types/node-rsa': 1.1.4
|
||||
'@types/readable-stream': 4.0.12
|
||||
aes-js: 3.1.2
|
||||
buffer-equal: 1.0.1
|
||||
debug: 4.4.0(supports-color@8.1.1)
|
||||
endian-toggle: 0.0.0
|
||||
lodash.merge: 4.6.2
|
||||
minecraft-data: 3.83.1
|
||||
minecraft-folder-path: 1.2.0
|
||||
node-fetch: 2.7.0(encoding@0.1.13)
|
||||
node-rsa: 0.4.2
|
||||
prismarine-auth: 2.4.2(encoding@0.1.13)
|
||||
prismarine-chat: 1.10.1
|
||||
prismarine-nbt: 2.7.0
|
||||
prismarine-realms: 1.3.2(encoding@0.1.13)
|
||||
protodef: 1.18.0
|
||||
readable-stream: 4.5.2
|
||||
uuid-1345: 1.0.2
|
||||
yggdrasil: 1.7.0(encoding@0.1.13)
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
|
||||
minecraft-wrap@1.5.1(encoding@0.1.13):
|
||||
dependencies:
|
||||
debug: 4.4.0(supports-color@8.1.1)
|
||||
|
|
@ -18043,7 +18073,7 @@ snapshots:
|
|||
mineflayer-pathfinder@2.4.4:
|
||||
dependencies:
|
||||
minecraft-data: 3.83.1
|
||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0)
|
||||
prismarine-entity: 2.3.1
|
||||
prismarine-item: 1.16.0
|
||||
prismarine-nbt: 2.5.0
|
||||
|
|
@ -18055,7 +18085,7 @@ snapshots:
|
|||
minecraft-data: 3.83.1
|
||||
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/5ec3dd4b367fcc039fbcb3edd214fe3cf8178a6d(patch_hash=dkeyukcqlupmk563gwxsmjr3yu)(encoding@0.1.13)
|
||||
prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0)
|
||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0)
|
||||
prismarine-chat: 1.10.1
|
||||
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1)
|
||||
prismarine-entity: 2.3.1
|
||||
|
|
@ -18078,7 +18108,7 @@ snapshots:
|
|||
minecraft-data: 3.83.1
|
||||
minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/5ec3dd4b367fcc039fbcb3edd214fe3cf8178a6d(patch_hash=dkeyukcqlupmk563gwxsmjr3yu)(encoding@0.1.13)
|
||||
prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0)
|
||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0)
|
||||
prismarine-chat: 1.10.1
|
||||
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1)
|
||||
prismarine-entity: 2.3.1
|
||||
|
|
@ -18867,7 +18897,7 @@ snapshots:
|
|||
minecraft-data: 3.83.1
|
||||
prismarine-registry: 1.11.0
|
||||
|
||||
prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9:
|
||||
prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0):
|
||||
dependencies:
|
||||
minecraft-data: 3.83.1
|
||||
prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0)
|
||||
|
|
@ -18875,6 +18905,8 @@ snapshots:
|
|||
prismarine-item: 1.16.0
|
||||
prismarine-nbt: 2.5.0
|
||||
prismarine-registry: 1.11.0
|
||||
transitivePeerDependencies:
|
||||
- prismarine-registry
|
||||
|
||||
prismarine-chat@1.10.1:
|
||||
dependencies:
|
||||
|
|
@ -18885,7 +18917,7 @@ snapshots:
|
|||
prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1):
|
||||
dependencies:
|
||||
prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0)
|
||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0)
|
||||
prismarine-nbt: 2.5.0
|
||||
prismarine-registry: 1.11.0
|
||||
smart-buffer: 4.2.0
|
||||
|
|
@ -18923,7 +18955,7 @@ snapshots:
|
|||
|
||||
prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.83.1):
|
||||
dependencies:
|
||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0)
|
||||
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1)
|
||||
prismarine-nbt: 2.5.0
|
||||
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
||||
|
|
@ -18947,13 +18979,13 @@ snapshots:
|
|||
prismarine-registry@1.11.0:
|
||||
dependencies:
|
||||
minecraft-data: 3.83.1
|
||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0)
|
||||
prismarine-nbt: 2.7.0
|
||||
|
||||
prismarine-schematic@1.2.3:
|
||||
dependencies:
|
||||
minecraft-data: 3.83.1
|
||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9
|
||||
prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/853c559bff2b402863ee9a75b125a3ca320838f9(prismarine-registry@1.11.0)
|
||||
prismarine-nbt: 2.5.0
|
||||
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
||||
vec3: 0.1.10
|
||||
|
|
|
|||
|
|
@ -109,7 +109,9 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
|||
geometryReceiveCount = {} as Record<number, number>
|
||||
allLoadedIn: undefined | number
|
||||
onWorldSwitched = [] as Array<() => void>
|
||||
|
||||
renderTimeMax = 0
|
||||
renderTimeAvg = 0
|
||||
renderTimeAvgCount = 0
|
||||
edgeChunks = {} as Record<string, boolean>
|
||||
lastAddChunk = null as null | {
|
||||
timeout: any
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
media: ThreeJsMedia
|
||||
waitingChunksToDisplay = {} as { [chunkKey: string]: SectionKey[] }
|
||||
camera: THREE.PerspectiveCamera
|
||||
renderTimeAvg = 0
|
||||
|
||||
get tilesRendered () {
|
||||
return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0)
|
||||
|
|
@ -403,6 +404,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
}
|
||||
|
||||
render (sizeChanged = false) {
|
||||
const start = performance.now()
|
||||
this.lastRendered = performance.now()
|
||||
this.cursorBlock.render()
|
||||
|
||||
|
|
@ -427,6 +429,11 @@ export class WorldRendererThree extends WorldRendererCommon {
|
|||
for (const onRender of this.onRender) {
|
||||
onRender()
|
||||
}
|
||||
const end = performance.now()
|
||||
const totalTime = end - start
|
||||
this.renderTimeAvgCount++
|
||||
this.renderTimeAvg = ((this.renderTimeAvg * (this.renderTimeAvgCount - 1)) + totalTime) / this.renderTimeAvgCount
|
||||
this.renderTimeMax = Math.max(this.renderTimeMax, totalTime)
|
||||
}
|
||||
|
||||
renderHead (position: Vec3, rotation: number, isWall: boolean, blockEntity) {
|
||||
|
|
|
|||
|
|
@ -51,6 +51,13 @@ export type AppQsParams = {
|
|||
replayStopOnError?: string
|
||||
replaySkipMissingOnTimeout?: string
|
||||
replayPacketsSenderDelay?: string
|
||||
|
||||
// Benchmark params
|
||||
openBenchmark?: string
|
||||
renderDistance?: string
|
||||
downloadBenchmark?: string
|
||||
benchmarkMapZipUrl?: string
|
||||
benchmarkPosition?: string
|
||||
}
|
||||
|
||||
export type AppQsParamsArray = {
|
||||
|
|
|
|||
179
src/benchmark.ts
Normal file
179
src/benchmark.ts
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
import { Vec3 } from 'vec3'
|
||||
import { WorldRendererCommon } from 'renderer/viewer/lib/worldrendererCommon'
|
||||
import prettyBytes from 'pretty-bytes'
|
||||
import { subscribe } from 'valtio'
|
||||
import { downloadAndOpenMapFromUrl } from './downloadAndOpenFile'
|
||||
import { activeModalStack, miscUiState } from './globalState'
|
||||
import { disabledSettings, options } from './optionsStorage'
|
||||
import { BenchmarkAdapterInfo, getAllInfoLines } from './benchmarkAdapter'
|
||||
import { appQueryParams } from './appParams'
|
||||
|
||||
const DEFAULT_RENDER_DISTANCE = 8
|
||||
|
||||
const fixtures = {
|
||||
default: {
|
||||
url: 'https://bucket.mcraft.fun/Future CITY 4.4-slim.zip',
|
||||
spawn: [-133, 87, 309] as [number, number, number],
|
||||
},
|
||||
}
|
||||
|
||||
Error.stackTraceLimit = Error.stackTraceLimit < 30 ? 30 : Error.stackTraceLimit
|
||||
|
||||
export const openBenchmark = async (renderDistance = DEFAULT_RENDER_DISTANCE) => {
|
||||
const fixture: {
|
||||
url: string
|
||||
spawn?: [number, number, number]
|
||||
} = appQueryParams.benchmarkMapZipUrl ? {
|
||||
url: appQueryParams.benchmarkMapZipUrl,
|
||||
spawn: appQueryParams.benchmarkPosition ? appQueryParams.benchmarkPosition.split(',').map(Number) as [number, number, number] : fixtures.default.spawn,
|
||||
} : fixtures.default
|
||||
|
||||
let memoryUsageAverage = 0
|
||||
let memoryUsageSamples = 0
|
||||
let memoryUsageWorst = 0
|
||||
setInterval(() => {
|
||||
const memoryUsage = (window.performance as any)?.memory?.usedJSHeapSize
|
||||
if (memoryUsage) {
|
||||
memoryUsageAverage = (memoryUsageAverage * memoryUsageSamples + memoryUsage) / (memoryUsageSamples + 1)
|
||||
memoryUsageSamples++
|
||||
if (memoryUsage > memoryUsageWorst) {
|
||||
memoryUsageWorst = memoryUsage
|
||||
}
|
||||
}
|
||||
}, 200)
|
||||
|
||||
let benchmarkName = `${fixture.url}`
|
||||
if (fixture.spawn) {
|
||||
benchmarkName += ` - ${fixture.spawn.join(',')}`
|
||||
}
|
||||
benchmarkName += ` - ${renderDistance}`
|
||||
const benchmarkAdapter: BenchmarkAdapterInfo = {
|
||||
get benchmarkName () {
|
||||
return benchmarkName
|
||||
},
|
||||
get worldLoadTimeSeconds () {
|
||||
return window.worldLoadTime
|
||||
},
|
||||
get mesherWorkersCount () {
|
||||
return (window.world as WorldRendererCommon).worldRendererConfig.mesherWorkers
|
||||
},
|
||||
get mesherProcessAvgMs () {
|
||||
return (window.world as WorldRendererCommon).workersProcessAverageTime
|
||||
},
|
||||
get mesherProcessWorstMs () {
|
||||
return (window.world as WorldRendererCommon).maxWorkersProcessTime
|
||||
},
|
||||
get averageRenderTimeMs () {
|
||||
return (window.world as WorldRendererCommon).renderTimeAvg
|
||||
},
|
||||
get worstRenderTimeMs () {
|
||||
return (window.world as WorldRendererCommon).renderTimeMax
|
||||
},
|
||||
get fpsAveragePrediction () {
|
||||
const avgRenderTime = (window.world as WorldRendererCommon).renderTimeAvg
|
||||
return 1000 / avgRenderTime
|
||||
},
|
||||
get fpsWorstPrediction () {
|
||||
const maxRenderTime = (window.world as WorldRendererCommon).renderTimeMax
|
||||
return 1000 / maxRenderTime
|
||||
},
|
||||
get fpsAverageReal () {
|
||||
return -1
|
||||
},
|
||||
get fpsWorstReal () {
|
||||
return -1
|
||||
},
|
||||
get memoryUsageAverage () {
|
||||
return prettyBytes(memoryUsageAverage)
|
||||
},
|
||||
get memoryUsageWorst () {
|
||||
return prettyBytes(memoryUsageWorst)
|
||||
},
|
||||
get gpuInfo () {
|
||||
return appViewer.rendererState.renderer
|
||||
},
|
||||
get hardwareConcurrency () {
|
||||
return navigator.hardwareConcurrency
|
||||
},
|
||||
get userAgent () {
|
||||
return navigator.userAgent
|
||||
},
|
||||
}
|
||||
window.benchmarkAdapter = benchmarkAdapter
|
||||
|
||||
disabledSettings.value.add('renderDistance')
|
||||
options.renderDistance = renderDistance
|
||||
void downloadAndOpenMapFromUrl(fixture.url, undefined, {
|
||||
connectEvents: {
|
||||
serverCreated () {
|
||||
if (fixture.spawn) {
|
||||
localServer!.spawnPoint = new Vec3(...fixture.spawn)
|
||||
localServer!.on('newPlayer', (player) => {
|
||||
player.on('dataLoaded', () => {
|
||||
player.position = new Vec3(...fixture.spawn!)
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
document.addEventListener('cypress-world-ready', () => {
|
||||
let stats = getAllInfoLines(window.benchmarkAdapter)
|
||||
if (appQueryParams.downloadBenchmark) {
|
||||
const a = document.createElement('a')
|
||||
a.href = 'data:text/plain;charset=utf-8,' + encodeURIComponent(stats.join('\n'))
|
||||
a.download = `benchmark-${appViewer.backend?.id}.txt`
|
||||
a.click()
|
||||
}
|
||||
|
||||
const panel = document.createElement('div')
|
||||
panel.style.position = 'fixed'
|
||||
panel.style.top = '10px'
|
||||
panel.style.right = '10px'
|
||||
panel.style.backgroundColor = 'rgba(0,0,0,0.8)'
|
||||
panel.style.color = 'white'
|
||||
panel.style.padding = '10px'
|
||||
panel.style.zIndex = '1000'
|
||||
panel.style.fontFamily = 'monospace'
|
||||
panel.id = 'benchmark-panel'
|
||||
|
||||
const pre = document.createElement('pre')
|
||||
panel.appendChild(pre)
|
||||
|
||||
pre.textContent = stats.join('\n')
|
||||
const updateStats = () => {
|
||||
stats = getAllInfoLines(window.benchmarkAdapter)
|
||||
pre.textContent = stats.join('\n')
|
||||
}
|
||||
|
||||
document.body.appendChild(panel)
|
||||
// setInterval(updateStats, 100)
|
||||
})
|
||||
}
|
||||
|
||||
document.addEventListener('pointerlockchange', (e) => {
|
||||
const panel = document.querySelector<HTMLDivElement>('#benchmark-panel')
|
||||
if (panel) {
|
||||
panel.hidden = !!document.pointerLockElement
|
||||
}
|
||||
})
|
||||
|
||||
subscribe(activeModalStack, () => {
|
||||
const panel = document.querySelector<HTMLDivElement>('#benchmark-panel')
|
||||
if (panel && activeModalStack.length > 1) {
|
||||
panel.hidden = true
|
||||
}
|
||||
})
|
||||
|
||||
export const registerOpenBenchmarkListener = () => {
|
||||
if (appQueryParams.openBenchmark) {
|
||||
void openBenchmark(appQueryParams.renderDistance ? +appQueryParams.renderDistance : undefined)
|
||||
}
|
||||
|
||||
window.addEventListener('keydown', (e) => {
|
||||
if (e.code === 'KeyB' && e.shiftKey && !miscUiState.gameLoaded && activeModalStack.length === 0) {
|
||||
e.preventDefault()
|
||||
void openBenchmark()
|
||||
}
|
||||
})
|
||||
}
|
||||
58
src/benchmarkAdapter.ts
Normal file
58
src/benchmarkAdapter.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
import { noCase } from 'change-case'
|
||||
|
||||
export interface BenchmarkAdapterInfo {
|
||||
benchmarkName: string
|
||||
// general load info
|
||||
worldLoadTimeSeconds: number
|
||||
|
||||
// mesher
|
||||
mesherWorkersCount: number
|
||||
mesherProcessAvgMs: number
|
||||
mesherProcessWorstMs: number
|
||||
|
||||
// rendering backend
|
||||
averageRenderTimeMs: number
|
||||
worstRenderTimeMs: number
|
||||
fpsAveragePrediction: number
|
||||
fpsWorstPrediction: number
|
||||
fpsAverageReal: number
|
||||
fpsWorstReal: number
|
||||
|
||||
// memory total
|
||||
memoryUsageAverage: string
|
||||
memoryUsageWorst: string
|
||||
|
||||
// context info
|
||||
gpuInfo: string
|
||||
hardwareConcurrency: number
|
||||
userAgent: string
|
||||
}
|
||||
|
||||
export const getAllInfo = (adapter: BenchmarkAdapterInfo) => {
|
||||
return Object.fromEntries(
|
||||
Object.entries(adapter).map(([key, value]) => {
|
||||
if (typeof value === 'function') {
|
||||
value = (value as () => any)()
|
||||
}
|
||||
if (typeof value === 'number') {
|
||||
value = value.toFixed(2)
|
||||
}
|
||||
return [noCase(key), value]
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export const getAllInfoLines = (adapter: BenchmarkAdapterInfo, delayed = false) => {
|
||||
const info = getAllInfo(adapter)
|
||||
if (delayed) {
|
||||
for (const key in info) {
|
||||
if (key !== 'fpsAveragePrediction' && key !== 'fpsAverageReal') {
|
||||
delete info[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Object.entries(info).map(([key, value]) => {
|
||||
return `${key}${delayed ? ' (delayed)' : ''}: ${value}`
|
||||
})
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@ import { packetsReplayState } from './react/state/packetsReplayState'
|
|||
import { createFullScreenProgressReporter } from './core/progressReporter'
|
||||
import { showNotification } from './react/NotificationProvider'
|
||||
import { resetAppStorage } from './react/appStorageProvider'
|
||||
import { ConnectOptions } from './connect'
|
||||
const { GoogleDriveFileSystem } = require('google-drive-browserfs/src/backends/GoogleDrive')
|
||||
|
||||
browserfs.install(window)
|
||||
|
|
@ -558,7 +559,7 @@ export const openWorldFromHttpDir = async (fileDescriptorUrls: string[]/* | und
|
|||
}
|
||||
|
||||
// todo rename method
|
||||
const openWorldZipInner = async (file: File | ArrayBuffer, name = file['name']) => {
|
||||
const openWorldZipInner = async (file: File | ArrayBuffer, name = file['name'], connectOptions?: Partial<ConnectOptions>) => {
|
||||
await new Promise<void>(async resolve => {
|
||||
browserfs.configure({
|
||||
// todo
|
||||
|
|
@ -603,7 +604,7 @@ const openWorldZipInner = async (file: File | ArrayBuffer, name = file['name'])
|
|||
}
|
||||
|
||||
if (availableWorlds.length === 1) {
|
||||
await loadSave(`/world/${availableWorlds[0]}`)
|
||||
await loadSave(`/world/${availableWorlds[0]}`, connectOptions)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export type ConnectOptions = {
|
|||
singleplayer?: any
|
||||
username: string
|
||||
proxy?: string
|
||||
botVersion?: any
|
||||
botVersion?: string
|
||||
serverOverrides?
|
||||
serverOverridesFlat?
|
||||
peerId?: string
|
||||
|
|
@ -23,7 +23,6 @@ export type ConnectOptions = {
|
|||
onSuccessfulPlay?: () => void
|
||||
autoLoginPassword?: string
|
||||
serverIndex?: string
|
||||
/** If true, will show a UI to authenticate with a new account */
|
||||
authenticatedAccount?: AuthenticatedAccount | true
|
||||
peerOptions?: any
|
||||
viewerWsConnect?: string
|
||||
|
|
@ -31,6 +30,15 @@ export type ConnectOptions = {
|
|||
|
||||
/** Will enable local replay server */
|
||||
worldStateFileContents?: string
|
||||
|
||||
connectEvents?: {
|
||||
serverCreated?: () => void
|
||||
// connect: () => void;
|
||||
// disconnect: () => void;
|
||||
// error: (err: any) => void;
|
||||
// ready: () => void;
|
||||
// end: () => void;
|
||||
}
|
||||
}
|
||||
|
||||
export const getVersionAutoSelect = (autoVersionSelect = options.serversAutoVersionSelect) => {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { setLoadingScreenStatus } from './appStatus'
|
|||
import { appQueryParams, appQueryParamsArray } from './appParams'
|
||||
import { VALID_REPLAY_EXTENSIONS, openFile } from './packetsReplay/replayPackets'
|
||||
import { createFullScreenProgressReporter } from './core/progressReporter'
|
||||
import { ConnectOptions } from './connect'
|
||||
|
||||
export const getFixedFilesize = (bytes: number) => {
|
||||
return prettyBytes(bytes, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||
|
|
@ -65,24 +66,29 @@ const inner = async () => {
|
|||
await openWorldFromHttpDir(mapUrlDir, mapUrlDirBaseUrl ?? undefined)
|
||||
return true
|
||||
}
|
||||
|
||||
if (mapUrlDirGuess) {
|
||||
// await openWorldFromHttpDir(undefined, mapUrlDirGuess)
|
||||
return true
|
||||
}
|
||||
let mapUrl = appQueryParams.map
|
||||
const { texturepack } = appQueryParams
|
||||
|
||||
const { map, texturepack } = appQueryParams
|
||||
return downloadAndOpenMapFromUrl(map, texturepack)
|
||||
}
|
||||
|
||||
export const downloadAndOpenMapFromUrl = async (mapUrl: string | undefined, texturepackUrl: string | undefined, connectOptions?: Partial<ConnectOptions>) => {
|
||||
// fixme
|
||||
if (texturepack) mapUrl = texturepack
|
||||
if (texturepackUrl) mapUrl = texturepackUrl
|
||||
if (!mapUrl) return false
|
||||
|
||||
if (texturepack) {
|
||||
if (texturepackUrl) {
|
||||
await updateTexturePackInstalledState()
|
||||
if (resourcePackState.resourcePackInstalled) {
|
||||
if (!confirm(`You are going to install a new resource pack, which will REPLACE the current one: ${await getResourcePackNames()[0]} Continue?`)) return
|
||||
}
|
||||
}
|
||||
const name = mapUrl.slice(mapUrl.lastIndexOf('/') + 1).slice(-25)
|
||||
const downloadThing = texturepack ? 'texturepack' : 'world'
|
||||
const downloadThing = texturepackUrl ? 'texturepack' : 'world'
|
||||
setLoadingScreenStatus(`Downloading ${downloadThing} ${name}...`)
|
||||
|
||||
const response = await fetch(mapUrl)
|
||||
|
|
@ -115,25 +121,25 @@ const inner = async () => {
|
|||
const progress = contentLength ? (downloadedBytes / contentLength) * 100 : undefined
|
||||
setLoadingScreenStatus(`Download ${downloadThing} progress: ${progress === undefined ? '?' : Math.floor(progress)}% (${getFixedFilesize(downloadedBytes)} / ${contentLength && getFixedFilesize(contentLength)})`, false, true)
|
||||
|
||||
|
||||
// Pass the received data to the controller
|
||||
controller.enqueue(value)
|
||||
}
|
||||
},
|
||||
})).arrayBuffer()
|
||||
if (texturepack) {
|
||||
if (texturepackUrl) {
|
||||
const name = mapUrl.slice(mapUrl.lastIndexOf('/') + 1).slice(-30)
|
||||
await installResourcepackPack(buffer, createFullScreenProgressReporter(), name)
|
||||
} else {
|
||||
await openWorldZip(buffer)
|
||||
await openWorldZip(buffer, undefined, connectOptions)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export default async () => {
|
||||
try {
|
||||
return await inner()
|
||||
} catch (err) {
|
||||
setLoadingScreenStatus(`Failed to download. Either refresh page or remove map param from URL. Reason: ${err.message}`)
|
||||
setLoadingScreenStatus(`Failed to download/open. Either refresh page or remove map param from URL. Reason: ${err.message}`)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
46
src/index.ts
46
src/index.ts
|
|
@ -96,6 +96,7 @@ import { localRelayServerPlugin } from './mineflayer/plugins/packetsRecording'
|
|||
import { createConsoleLogProgressReporter, createFullScreenProgressReporter, ProgressReporter } from './core/progressReporter'
|
||||
import { appViewer } from './appViewer'
|
||||
import './appViewerLoad'
|
||||
import { registerOpenBenchmarkListener } from './benchmark'
|
||||
|
||||
window.debug = debug
|
||||
window.beforeRenderFrame = []
|
||||
|
|
@ -119,13 +120,22 @@ function hideCurrentScreens () {
|
|||
insertActiveModalStack('', [])
|
||||
}
|
||||
|
||||
const loadSingleplayer = (serverOverrides = {}, flattenedServerOverrides = {}) => {
|
||||
const loadSingleplayer = (serverOverrides = {}, flattenedServerOverrides = {}, connectOptions?: Partial<ConnectOptions>) => {
|
||||
const serverSettingsQsRaw = appQueryParamsArray.serverSetting ?? []
|
||||
const serverSettingsQs = serverSettingsQsRaw.map(x => x.split(':')).reduce<Record<string, string>>((acc, [key, value]) => {
|
||||
acc[key] = JSON.parse(value)
|
||||
return acc
|
||||
}, {})
|
||||
void connect({ singleplayer: true, username: options.localUsername, serverOverrides, serverOverridesFlat: { ...flattenedServerOverrides, ...serverSettingsQs } })
|
||||
void connect({
|
||||
singleplayer: true,
|
||||
username: options.localUsername,
|
||||
serverOverrides,
|
||||
serverOverridesFlat: {
|
||||
...flattenedServerOverrides,
|
||||
...serverSettingsQs
|
||||
},
|
||||
...connectOptions
|
||||
})
|
||||
}
|
||||
function listenGlobalEvents () {
|
||||
window.addEventListener('connect', e => {
|
||||
|
|
@ -133,7 +143,9 @@ function listenGlobalEvents () {
|
|||
void connect(options)
|
||||
})
|
||||
window.addEventListener('singleplayer', (e) => {
|
||||
loadSingleplayer((e as CustomEvent).detail)
|
||||
const { detail } = (e as CustomEvent)
|
||||
const { connectOptions, ...rest } = detail
|
||||
loadSingleplayer(rest, {}, connectOptions)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -348,6 +360,7 @@ export async function connect (connectOptions: ConnectOptions) {
|
|||
// flying-squid: 'login' -> player.login -> now sends 'login' event to the client (handled in many plugins in mineflayer) -> then 'update_health' is sent which emits 'spawn' in mineflayer
|
||||
|
||||
localServer = window.localServer = window.server = startLocalServer(serverOptions)
|
||||
connectOptions?.connectEvents?.serverCreated?.()
|
||||
// todo need just to call quit if started
|
||||
// loadingScreen.maybeRecoverable = false
|
||||
// init world, todo: do it for any async plugins
|
||||
|
|
@ -644,13 +657,15 @@ export async function connect (connectOptions: ConnectOptions) {
|
|||
setLoadingScreenStatus('Loading world')
|
||||
})
|
||||
|
||||
const loadStart = Date.now()
|
||||
let worldWasReady = false
|
||||
const waitForChunksToLoad = async (progress?: ProgressReporter) => {
|
||||
await new Promise<void>(resolve => {
|
||||
if (worldWasReady) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
const unsub = subscribe(appViewer.rendererState, () => {
|
||||
if (worldWasReady) return
|
||||
if (appViewer.rendererState.world.allChunksLoaded) {
|
||||
if (appViewer.rendererState.world.allChunksLoaded && appViewer.nonReactiveState.world.chunksTotalNumber) {
|
||||
worldWasReady = true
|
||||
resolve()
|
||||
unsub()
|
||||
|
|
@ -662,11 +677,6 @@ export async function connect (connectOptions: ConnectOptions) {
|
|||
})
|
||||
}
|
||||
|
||||
void waitForChunksToLoad().then(() => {
|
||||
console.log('All chunks done and ready! Time from renderer connect to ready', (Date.now() - loadStart) / 1000, 's')
|
||||
document.dispatchEvent(new Event('cypress-world-ready'))
|
||||
})
|
||||
|
||||
const spawnEarlier = !singleplayer && !p2pMultiplayer
|
||||
const displayWorld = async () => {
|
||||
if (resourcePackState.isServerInstalling) {
|
||||
|
|
@ -679,11 +689,18 @@ export async function connect (connectOptions: ConnectOptions) {
|
|||
})
|
||||
await appViewer.resourcesManager.promiseAssetsReady
|
||||
}
|
||||
console.log('try to focus window')
|
||||
window.focus?.()
|
||||
errorAbortController.abort()
|
||||
if (appStatusState.isError) return
|
||||
|
||||
const loadWorldStart = Date.now()
|
||||
console.log('try to focus window')
|
||||
window.focus?.()
|
||||
void waitForChunksToLoad().then(() => {
|
||||
window.worldLoadTime = (Date.now() - loadWorldStart) / 1000
|
||||
console.log('All chunks done and ready! Time from renderer connect to ready', (Date.now() - loadWorldStart) / 1000, 's')
|
||||
document.dispatchEvent(new Event('cypress-world-ready'))
|
||||
})
|
||||
|
||||
try {
|
||||
if (p2pConnectTimeout) clearTimeout(p2pConnectTimeout)
|
||||
playerState.onlineMode = !!connectOptions.authenticatedAccount
|
||||
|
|
@ -932,7 +949,7 @@ if (!reconnectOptions) {
|
|||
}
|
||||
}, (err) => {
|
||||
console.error(err)
|
||||
alert(`Failed to download file: ${err}`)
|
||||
alert(`Something went wrong: ${err}`)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -945,3 +962,4 @@ if (initialLoader) {
|
|||
window.pageLoaded = true
|
||||
|
||||
void possiblyHandleStateVariable()
|
||||
registerOpenBenchmarkListener()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { isMajorVersionGreater } from './utils'
|
|||
|
||||
import { activeModalStacks, insertActiveModalStack, miscUiState } from './globalState'
|
||||
import supportedVersions from './supportedVersions.mjs'
|
||||
import { ConnectOptions } from './connect'
|
||||
import { appQueryParams } from './appParams'
|
||||
|
||||
// todo include name of opened handle (zip)!
|
||||
|
|
@ -49,7 +50,7 @@ export const readLevelDat = async (path) => {
|
|||
return { levelDat, dataRaw: parsed.value.Data!.value as Record<string, any> }
|
||||
}
|
||||
|
||||
export const loadSave = async (root = '/world') => {
|
||||
export const loadSave = async (root = '/world', connectOptions?: Partial<ConnectOptions>) => {
|
||||
// todo test
|
||||
if (miscUiState.gameLoaded) {
|
||||
await disconnect()
|
||||
|
|
@ -194,7 +195,8 @@ export const loadSave = async (root = '/world') => {
|
|||
} : {},
|
||||
...root === '/world' ? {} : {
|
||||
'worldFolder': root
|
||||
}
|
||||
},
|
||||
connectOptions
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,71 @@
|
|||
import { subscribeKey } from 'valtio/utils'
|
||||
import { preventThrottlingWithSound } from '../core/timers'
|
||||
import { options } from '../optionsStorage'
|
||||
|
||||
customEvents.on('mineflayerBotCreated', () => {
|
||||
if (options.preventBackgroundTimeoutKick) {
|
||||
const unsub = preventThrottlingWithSound()
|
||||
bot.on('end', unsub)
|
||||
const abortController = new AbortController()
|
||||
|
||||
const maybeGoBackgroundKickPrevention = () => {
|
||||
if (options.preventBackgroundTimeoutKick && !bot.backgroundKickPrevention) {
|
||||
const unsub = preventThrottlingWithSound()
|
||||
bot.on('end', unsub)
|
||||
bot.backgroundKickPrevention = true
|
||||
}
|
||||
}
|
||||
maybeGoBackgroundKickPrevention()
|
||||
subscribeKey(options, 'preventBackgroundTimeoutKick', (value) => {
|
||||
maybeGoBackgroundKickPrevention()
|
||||
})
|
||||
|
||||
// wake lock
|
||||
const requestWakeLock = async () => {
|
||||
if (!('wakeLock' in navigator)) {
|
||||
console.warn('Wake Lock API is not supported in this browser')
|
||||
return
|
||||
}
|
||||
|
||||
if (options.preventSleep && !bot.wakeLock && !bot.lockRequested) {
|
||||
bot.lockRequested = true
|
||||
bot.wakeLock = await navigator.wakeLock.request('screen').finally(() => {
|
||||
bot.lockRequested = false
|
||||
})
|
||||
|
||||
bot.wakeLock.addEventListener('release', () => {
|
||||
bot.wakeLock = undefined
|
||||
}, {
|
||||
once: true,
|
||||
})
|
||||
}
|
||||
|
||||
if (!options.preventSleep && bot.wakeLock) {
|
||||
void bot.wakeLock.release()
|
||||
}
|
||||
}
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
// we are back to the tab, request wake lock again
|
||||
void requestWakeLock()
|
||||
}
|
||||
}, {
|
||||
signal: abortController.signal,
|
||||
})
|
||||
void requestWakeLock()
|
||||
subscribeKey(options, 'preventSleep', (value) => {
|
||||
void requestWakeLock()
|
||||
})
|
||||
|
||||
bot.on('end', () => {
|
||||
if (bot.wakeLock) {
|
||||
void bot.wakeLock.release()
|
||||
}
|
||||
abortController.abort()
|
||||
})
|
||||
})
|
||||
|
||||
declare module 'mineflayer' {
|
||||
interface Bot {
|
||||
backgroundKickPrevention?: boolean
|
||||
wakeLock?: WakeLockSentinel
|
||||
lockRequested?: boolean
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -526,7 +526,11 @@ export const guiOptionsScheme: {
|
|||
},
|
||||
},
|
||||
{
|
||||
preventBackgroundTimeoutKick: {}
|
||||
preventBackgroundTimeoutKick: {},
|
||||
preventSleep: {
|
||||
disabledReason: navigator.wakeLock ? undefined : 'Your browser does not support wake lock API',
|
||||
enableWarning: 'When connected to a server, prevent PC from sleeping or screen dimming. Useful for purpusely staying AFK for long time. Some events might still prevent this like loosing tab focus or going low power mode.',
|
||||
},
|
||||
},
|
||||
{
|
||||
custom () {
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ const defaultOptions = {
|
|||
waitForChunksRender: 'sp-only' as 'sp-only' | boolean,
|
||||
jeiEnabled: true as boolean | Array<'creative' | 'survival' | 'adventure' | 'spectator'>,
|
||||
preventBackgroundTimeoutKick: false,
|
||||
preventSleep: false,
|
||||
|
||||
// antiAliasing: false,
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,8 @@ export const defaultIndicatorsState = {
|
|||
readonlyFiles: false,
|
||||
writingFiles: false, // saving
|
||||
appHasErrors: false,
|
||||
connectionIssues: 0
|
||||
connectionIssues: 0,
|
||||
preventSleep: false,
|
||||
}
|
||||
|
||||
const indicatorIcons: Record<keyof typeof defaultIndicatorsState, string> = {
|
||||
|
|
@ -56,6 +57,7 @@ const indicatorIcons: Record<keyof typeof defaultIndicatorsState, string> = {
|
|||
appHasErrors: 'alert',
|
||||
readonlyFiles: 'file-off',
|
||||
connectionIssues: pixelartIcons['cellular-signal-off'],
|
||||
preventSleep: pixelartIcons.moon,
|
||||
}
|
||||
|
||||
const colorOverrides = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { proxy, subscribe, useSnapshot } from 'valtio'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { subscribeKey } from 'valtio/utils'
|
||||
import { inGameError } from '../utils'
|
||||
import { fsState } from '../loadSave'
|
||||
|
|
@ -51,6 +51,7 @@ const getEffectIndex = (newEffect: EffectType) => {
|
|||
}
|
||||
|
||||
export default () => {
|
||||
const [dummyState, setDummyState] = useState(false)
|
||||
const stateIndicators = useSnapshot(state.indicators)
|
||||
const chunksLoading = !useSnapshot(appViewer.rendererState).world.allChunksLoaded
|
||||
const { mesherWork } = useSnapshot(appViewer.rendererState).world
|
||||
|
|
@ -66,12 +67,21 @@ export default () => {
|
|||
appHasErrors: hasErrors,
|
||||
connectionIssues: poorConnection ? 1 : noConnection ? 2 : 0,
|
||||
chunksLoading,
|
||||
preventSleep: !!bot.wakeLock,
|
||||
// mesherWork,
|
||||
...stateIndicators,
|
||||
}
|
||||
|
||||
const effects = useSnapshot(state.effects)
|
||||
|
||||
useEffect(() => {
|
||||
// update bot related states
|
||||
const interval = setInterval(() => {
|
||||
setDummyState(s => !s)
|
||||
}, 1000)
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
useMemo(() => {
|
||||
const effectsImages = Object.fromEntries(loadedData.effectsArray.map((effect) => {
|
||||
const nameKebab = effect.name.replaceAll(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`).slice(1)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue