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'
|
import { defineConfig } from 'cypress'
|
||||||
|
|
||||||
|
const isPerformanceTest = process.env.PERFORMANCE_TEST === 'true'
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
video: false,
|
video: false,
|
||||||
chromeWebSecurity: false,
|
chromeWebSecurity: false,
|
||||||
|
|
@ -32,7 +34,7 @@ export default defineConfig({
|
||||||
return require('./cypress/plugins/index.js')(on, config)
|
return require('./cypress/plugins/index.js')(on, config)
|
||||||
},
|
},
|
||||||
baseUrl: 'http://localhost:8080',
|
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__/*'],
|
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",
|
"prepare-project": "tsx scripts/genShims.ts && tsx scripts/makeOptimizedMcData.mjs && tsx scripts/genLargeDataAliases.ts",
|
||||||
"check-build": "pnpm prepare-project && tsc && pnpm build",
|
"check-build": "pnpm prepare-project && tsc && pnpm build",
|
||||||
"test:cypress": "cypress run",
|
"test:cypress": "cypress run",
|
||||||
|
"test:benchmark": "PERFORMANCE_TEST=true cypress run",
|
||||||
"test:cypress:open": "cypress open",
|
"test:cypress:open": "cypress open",
|
||||||
"test-unit": "vitest",
|
"test-unit": "vitest",
|
||||||
"test:e2e": "start-test http-get://localhost:8080 test:cypress",
|
"test:e2e": "start-test http-get://localhost:8080 test:cypress",
|
||||||
|
|
@ -75,7 +76,7 @@
|
||||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"filesize": "^10.0.12",
|
"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",
|
"fs-extra": "^11.1.1",
|
||||||
"google-drive-browserfs": "github:zardoy/browserfs#google-drive",
|
"google-drive-browserfs": "github:zardoy/browserfs#google-drive",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
|
|
|
||||||
64
pnpm-lock.yaml
generated
64
pnpm-lock.yaml
generated
|
|
@ -120,8 +120,8 @@ importers:
|
||||||
specifier: ^10.0.12
|
specifier: ^10.0.12
|
||||||
version: 10.0.12
|
version: 10.0.12
|
||||||
flying-squid:
|
flying-squid:
|
||||||
specifier: npm:@zardoy/flying-squid@^0.0.51
|
specifier: npm:@zardoy/flying-squid@^0.0.58
|
||||||
version: '@zardoy/flying-squid@0.0.51(encoding@0.1.13)'
|
version: '@zardoy/flying-squid@0.0.58(encoding@0.1.13)'
|
||||||
fs-extra:
|
fs-extra:
|
||||||
specifier: ^11.1.1
|
specifier: ^11.1.1
|
||||||
version: 11.1.1
|
version: 11.1.1
|
||||||
|
|
@ -438,7 +438,7 @@ importers:
|
||||||
version: 1.3.6
|
version: 1.3.6
|
||||||
prismarine-block:
|
prismarine-block:
|
||||||
specifier: github:zardoy/prismarine-block#next-era
|
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:
|
prismarine-chunk:
|
||||||
specifier: github:zardoy/prismarine-chunk#master
|
specifier: github:zardoy/prismarine-chunk#master
|
||||||
version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1)
|
version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1)
|
||||||
|
|
@ -3541,8 +3541,8 @@ packages:
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
'@zardoy/flying-squid@0.0.51':
|
'@zardoy/flying-squid@0.0.58':
|
||||||
resolution: {integrity: sha512-HHZ79H9NkS44lL9vk6gVEuJDJqj88gpiBt9Ihh5p4rHXTVbRid95riiNK5dD0kHI94P5/DXdtNalvmJDPU86oQ==}
|
resolution: {integrity: sha512-qkSoaYRpVQaAvcVgZDTe0i4PxaK2l2B6i7GfRCEsyYFl3UaNQYBwwocXqLrIwhsc63bwXa0XQe8UNUubz+A4eA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
|
@ -6926,6 +6926,11 @@ packages:
|
||||||
version: 1.54.0
|
version: 1.54.0
|
||||||
engines: {node: '>=22'}
|
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:
|
minecraft-wrap@1.5.1:
|
||||||
resolution: {integrity: sha512-7DZ2WhrcRD3fUMau84l9Va0KWzV92SHNdB7mnNdNhgXID2aW6pjWuYPZi8MepEBemA4XKKdnDx7HmhTbkoiR8A==}
|
resolution: {integrity: sha512-7DZ2WhrcRD3fUMau84l9Va0KWzV92SHNdB7mnNdNhgXID2aW6pjWuYPZi8MepEBemA4XKKdnDx7HmhTbkoiR8A==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
@ -13652,7 +13657,7 @@ snapshots:
|
||||||
- encoding
|
- encoding
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
'@zardoy/flying-squid@0.0.51(encoding@0.1.13)':
|
'@zardoy/flying-squid@0.0.58(encoding@0.1.13)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tootallnate/once': 2.0.0
|
'@tootallnate/once': 2.0.0
|
||||||
chalk: 5.3.0
|
chalk: 5.3.0
|
||||||
|
|
@ -13663,14 +13668,14 @@ snapshots:
|
||||||
flatmap: 0.0.3
|
flatmap: 0.0.3
|
||||||
long: 5.2.3
|
long: 5.2.3
|
||||||
minecraft-data: 3.83.1
|
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
|
mkdirp: 2.1.6
|
||||||
node-gzip: 1.1.2
|
node-gzip: 1.1.2
|
||||||
node-rsa: 1.1.1
|
node-rsa: 1.1.1
|
||||||
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1)
|
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1)
|
||||||
prismarine-entity: 2.3.1
|
prismarine-entity: 2.3.1
|
||||||
prismarine-item: 1.16.0
|
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-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.83.1)
|
||||||
prismarine-windows: 2.9.0
|
prismarine-windows: 2.9.0
|
||||||
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
||||||
|
|
@ -17981,6 +17986,31 @@ snapshots:
|
||||||
- encoding
|
- encoding
|
||||||
- supports-color
|
- 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):
|
minecraft-wrap@1.5.1(encoding@0.1.13):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.0(supports-color@8.1.1)
|
debug: 4.4.0(supports-color@8.1.1)
|
||||||
|
|
@ -18043,7 +18073,7 @@ snapshots:
|
||||||
mineflayer-pathfinder@2.4.4:
|
mineflayer-pathfinder@2.4.4:
|
||||||
dependencies:
|
dependencies:
|
||||||
minecraft-data: 3.83.1
|
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-entity: 2.3.1
|
||||||
prismarine-item: 1.16.0
|
prismarine-item: 1.16.0
|
||||||
prismarine-nbt: 2.5.0
|
prismarine-nbt: 2.5.0
|
||||||
|
|
@ -18055,7 +18085,7 @@ snapshots:
|
||||||
minecraft-data: 3.83.1
|
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/5ec3dd4b367fcc039fbcb3edd214fe3cf8178a6d(patch_hash=dkeyukcqlupmk563gwxsmjr3yu)(encoding@0.1.13)
|
||||||
prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0)
|
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-chat: 1.10.1
|
||||||
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1)
|
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1)
|
||||||
prismarine-entity: 2.3.1
|
prismarine-entity: 2.3.1
|
||||||
|
|
@ -18078,7 +18108,7 @@ snapshots:
|
||||||
minecraft-data: 3.83.1
|
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/5ec3dd4b367fcc039fbcb3edd214fe3cf8178a6d(patch_hash=dkeyukcqlupmk563gwxsmjr3yu)(encoding@0.1.13)
|
||||||
prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0)
|
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-chat: 1.10.1
|
||||||
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1)
|
prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1)
|
||||||
prismarine-entity: 2.3.1
|
prismarine-entity: 2.3.1
|
||||||
|
|
@ -18867,7 +18897,7 @@ snapshots:
|
||||||
minecraft-data: 3.83.1
|
minecraft-data: 3.83.1
|
||||||
prismarine-registry: 1.11.0
|
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:
|
dependencies:
|
||||||
minecraft-data: 3.83.1
|
minecraft-data: 3.83.1
|
||||||
prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0)
|
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-item: 1.16.0
|
||||||
prismarine-nbt: 2.5.0
|
prismarine-nbt: 2.5.0
|
||||||
prismarine-registry: 1.11.0
|
prismarine-registry: 1.11.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- prismarine-registry
|
||||||
|
|
||||||
prismarine-chat@1.10.1:
|
prismarine-chat@1.10.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|
@ -18885,7 +18917,7 @@ snapshots:
|
||||||
prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1):
|
prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
prismarine-biome: 1.3.0(minecraft-data@3.83.1)(prismarine-registry@1.11.0)
|
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-nbt: 2.5.0
|
||||||
prismarine-registry: 1.11.0
|
prismarine-registry: 1.11.0
|
||||||
smart-buffer: 4.2.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):
|
prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/1d548fac63fe977c8281f0a9a522b37e4d92d0b7(minecraft-data@3.83.1):
|
||||||
dependencies:
|
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-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/e68e9a423b5b1907535878fb636f12c28a1a9374(minecraft-data@3.83.1)
|
||||||
prismarine-nbt: 2.5.0
|
prismarine-nbt: 2.5.0
|
||||||
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
||||||
|
|
@ -18947,13 +18979,13 @@ snapshots:
|
||||||
prismarine-registry@1.11.0:
|
prismarine-registry@1.11.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
minecraft-data: 3.83.1
|
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-nbt: 2.7.0
|
||||||
|
|
||||||
prismarine-schematic@1.2.3:
|
prismarine-schematic@1.2.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
minecraft-data: 3.83.1
|
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-nbt: 2.5.0
|
||||||
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/ab2146c9933eef3247c3f64446de4ccc2c484c7c
|
||||||
vec3: 0.1.10
|
vec3: 0.1.10
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,9 @@ export abstract class WorldRendererCommon<WorkerSend = any, WorkerReceive = any>
|
||||||
geometryReceiveCount = {} as Record<number, number>
|
geometryReceiveCount = {} as Record<number, number>
|
||||||
allLoadedIn: undefined | number
|
allLoadedIn: undefined | number
|
||||||
onWorldSwitched = [] as Array<() => void>
|
onWorldSwitched = [] as Array<() => void>
|
||||||
|
renderTimeMax = 0
|
||||||
|
renderTimeAvg = 0
|
||||||
|
renderTimeAvgCount = 0
|
||||||
edgeChunks = {} as Record<string, boolean>
|
edgeChunks = {} as Record<string, boolean>
|
||||||
lastAddChunk = null as null | {
|
lastAddChunk = null as null | {
|
||||||
timeout: any
|
timeout: any
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ export class WorldRendererThree extends WorldRendererCommon {
|
||||||
media: ThreeJsMedia
|
media: ThreeJsMedia
|
||||||
waitingChunksToDisplay = {} as { [chunkKey: string]: SectionKey[] }
|
waitingChunksToDisplay = {} as { [chunkKey: string]: SectionKey[] }
|
||||||
camera: THREE.PerspectiveCamera
|
camera: THREE.PerspectiveCamera
|
||||||
|
renderTimeAvg = 0
|
||||||
|
|
||||||
get tilesRendered () {
|
get tilesRendered () {
|
||||||
return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0)
|
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) {
|
render (sizeChanged = false) {
|
||||||
|
const start = performance.now()
|
||||||
this.lastRendered = performance.now()
|
this.lastRendered = performance.now()
|
||||||
this.cursorBlock.render()
|
this.cursorBlock.render()
|
||||||
|
|
||||||
|
|
@ -427,6 +429,11 @@ export class WorldRendererThree extends WorldRendererCommon {
|
||||||
for (const onRender of this.onRender) {
|
for (const onRender of this.onRender) {
|
||||||
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) {
|
renderHead (position: Vec3, rotation: number, isWall: boolean, blockEntity) {
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,13 @@ export type AppQsParams = {
|
||||||
replayStopOnError?: string
|
replayStopOnError?: string
|
||||||
replaySkipMissingOnTimeout?: string
|
replaySkipMissingOnTimeout?: string
|
||||||
replayPacketsSenderDelay?: string
|
replayPacketsSenderDelay?: string
|
||||||
|
|
||||||
|
// Benchmark params
|
||||||
|
openBenchmark?: string
|
||||||
|
renderDistance?: string
|
||||||
|
downloadBenchmark?: string
|
||||||
|
benchmarkMapZipUrl?: string
|
||||||
|
benchmarkPosition?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AppQsParamsArray = {
|
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 { createFullScreenProgressReporter } from './core/progressReporter'
|
||||||
import { showNotification } from './react/NotificationProvider'
|
import { showNotification } from './react/NotificationProvider'
|
||||||
import { resetAppStorage } from './react/appStorageProvider'
|
import { resetAppStorage } from './react/appStorageProvider'
|
||||||
|
import { ConnectOptions } from './connect'
|
||||||
const { GoogleDriveFileSystem } = require('google-drive-browserfs/src/backends/GoogleDrive')
|
const { GoogleDriveFileSystem } = require('google-drive-browserfs/src/backends/GoogleDrive')
|
||||||
|
|
||||||
browserfs.install(window)
|
browserfs.install(window)
|
||||||
|
|
@ -558,7 +559,7 @@ export const openWorldFromHttpDir = async (fileDescriptorUrls: string[]/* | und
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo rename method
|
// 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 => {
|
await new Promise<void>(async resolve => {
|
||||||
browserfs.configure({
|
browserfs.configure({
|
||||||
// todo
|
// todo
|
||||||
|
|
@ -603,7 +604,7 @@ const openWorldZipInner = async (file: File | ArrayBuffer, name = file['name'])
|
||||||
}
|
}
|
||||||
|
|
||||||
if (availableWorlds.length === 1) {
|
if (availableWorlds.length === 1) {
|
||||||
await loadSave(`/world/${availableWorlds[0]}`)
|
await loadSave(`/world/${availableWorlds[0]}`, connectOptions)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ export type ConnectOptions = {
|
||||||
singleplayer?: any
|
singleplayer?: any
|
||||||
username: string
|
username: string
|
||||||
proxy?: string
|
proxy?: string
|
||||||
botVersion?: any
|
botVersion?: string
|
||||||
serverOverrides?
|
serverOverrides?
|
||||||
serverOverridesFlat?
|
serverOverridesFlat?
|
||||||
peerId?: string
|
peerId?: string
|
||||||
|
|
@ -23,7 +23,6 @@ export type ConnectOptions = {
|
||||||
onSuccessfulPlay?: () => void
|
onSuccessfulPlay?: () => void
|
||||||
autoLoginPassword?: string
|
autoLoginPassword?: string
|
||||||
serverIndex?: string
|
serverIndex?: string
|
||||||
/** If true, will show a UI to authenticate with a new account */
|
|
||||||
authenticatedAccount?: AuthenticatedAccount | true
|
authenticatedAccount?: AuthenticatedAccount | true
|
||||||
peerOptions?: any
|
peerOptions?: any
|
||||||
viewerWsConnect?: string
|
viewerWsConnect?: string
|
||||||
|
|
@ -31,6 +30,15 @@ export type ConnectOptions = {
|
||||||
|
|
||||||
/** Will enable local replay server */
|
/** Will enable local replay server */
|
||||||
worldStateFileContents?: string
|
worldStateFileContents?: string
|
||||||
|
|
||||||
|
connectEvents?: {
|
||||||
|
serverCreated?: () => void
|
||||||
|
// connect: () => void;
|
||||||
|
// disconnect: () => void;
|
||||||
|
// error: (err: any) => void;
|
||||||
|
// ready: () => void;
|
||||||
|
// end: () => void;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getVersionAutoSelect = (autoVersionSelect = options.serversAutoVersionSelect) => {
|
export const getVersionAutoSelect = (autoVersionSelect = options.serversAutoVersionSelect) => {
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { setLoadingScreenStatus } from './appStatus'
|
||||||
import { appQueryParams, appQueryParamsArray } from './appParams'
|
import { appQueryParams, appQueryParamsArray } from './appParams'
|
||||||
import { VALID_REPLAY_EXTENSIONS, openFile } from './packetsReplay/replayPackets'
|
import { VALID_REPLAY_EXTENSIONS, openFile } from './packetsReplay/replayPackets'
|
||||||
import { createFullScreenProgressReporter } from './core/progressReporter'
|
import { createFullScreenProgressReporter } from './core/progressReporter'
|
||||||
|
import { ConnectOptions } from './connect'
|
||||||
|
|
||||||
export const getFixedFilesize = (bytes: number) => {
|
export const getFixedFilesize = (bytes: number) => {
|
||||||
return prettyBytes(bytes, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
return prettyBytes(bytes, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||||
|
|
@ -65,24 +66,29 @@ const inner = async () => {
|
||||||
await openWorldFromHttpDir(mapUrlDir, mapUrlDirBaseUrl ?? undefined)
|
await openWorldFromHttpDir(mapUrlDir, mapUrlDirBaseUrl ?? undefined)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mapUrlDirGuess) {
|
if (mapUrlDirGuess) {
|
||||||
// await openWorldFromHttpDir(undefined, mapUrlDirGuess)
|
// await openWorldFromHttpDir(undefined, mapUrlDirGuess)
|
||||||
return true
|
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
|
// fixme
|
||||||
if (texturepack) mapUrl = texturepack
|
if (texturepackUrl) mapUrl = texturepackUrl
|
||||||
if (!mapUrl) return false
|
if (!mapUrl) return false
|
||||||
|
|
||||||
if (texturepack) {
|
if (texturepackUrl) {
|
||||||
await updateTexturePackInstalledState()
|
await updateTexturePackInstalledState()
|
||||||
if (resourcePackState.resourcePackInstalled) {
|
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
|
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 name = mapUrl.slice(mapUrl.lastIndexOf('/') + 1).slice(-25)
|
||||||
const downloadThing = texturepack ? 'texturepack' : 'world'
|
const downloadThing = texturepackUrl ? 'texturepack' : 'world'
|
||||||
setLoadingScreenStatus(`Downloading ${downloadThing} ${name}...`)
|
setLoadingScreenStatus(`Downloading ${downloadThing} ${name}...`)
|
||||||
|
|
||||||
const response = await fetch(mapUrl)
|
const response = await fetch(mapUrl)
|
||||||
|
|
@ -115,25 +121,25 @@ const inner = async () => {
|
||||||
const progress = contentLength ? (downloadedBytes / contentLength) * 100 : undefined
|
const progress = contentLength ? (downloadedBytes / contentLength) * 100 : undefined
|
||||||
setLoadingScreenStatus(`Download ${downloadThing} progress: ${progress === undefined ? '?' : Math.floor(progress)}% (${getFixedFilesize(downloadedBytes)} / ${contentLength && getFixedFilesize(contentLength)})`, false, true)
|
setLoadingScreenStatus(`Download ${downloadThing} progress: ${progress === undefined ? '?' : Math.floor(progress)}% (${getFixedFilesize(downloadedBytes)} / ${contentLength && getFixedFilesize(contentLength)})`, false, true)
|
||||||
|
|
||||||
|
|
||||||
// Pass the received data to the controller
|
// Pass the received data to the controller
|
||||||
controller.enqueue(value)
|
controller.enqueue(value)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})).arrayBuffer()
|
})).arrayBuffer()
|
||||||
if (texturepack) {
|
if (texturepackUrl) {
|
||||||
const name = mapUrl.slice(mapUrl.lastIndexOf('/') + 1).slice(-30)
|
const name = mapUrl.slice(mapUrl.lastIndexOf('/') + 1).slice(-30)
|
||||||
await installResourcepackPack(buffer, createFullScreenProgressReporter(), name)
|
await installResourcepackPack(buffer, createFullScreenProgressReporter(), name)
|
||||||
} else {
|
} else {
|
||||||
await openWorldZip(buffer)
|
await openWorldZip(buffer, undefined, connectOptions)
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async () => {
|
export default async () => {
|
||||||
try {
|
try {
|
||||||
return await inner()
|
return await inner()
|
||||||
} catch (err) {
|
} 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
|
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 { createConsoleLogProgressReporter, createFullScreenProgressReporter, ProgressReporter } from './core/progressReporter'
|
||||||
import { appViewer } from './appViewer'
|
import { appViewer } from './appViewer'
|
||||||
import './appViewerLoad'
|
import './appViewerLoad'
|
||||||
|
import { registerOpenBenchmarkListener } from './benchmark'
|
||||||
|
|
||||||
window.debug = debug
|
window.debug = debug
|
||||||
window.beforeRenderFrame = []
|
window.beforeRenderFrame = []
|
||||||
|
|
@ -119,13 +120,22 @@ function hideCurrentScreens () {
|
||||||
insertActiveModalStack('', [])
|
insertActiveModalStack('', [])
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadSingleplayer = (serverOverrides = {}, flattenedServerOverrides = {}) => {
|
const loadSingleplayer = (serverOverrides = {}, flattenedServerOverrides = {}, connectOptions?: Partial<ConnectOptions>) => {
|
||||||
const serverSettingsQsRaw = appQueryParamsArray.serverSetting ?? []
|
const serverSettingsQsRaw = appQueryParamsArray.serverSetting ?? []
|
||||||
const serverSettingsQs = serverSettingsQsRaw.map(x => x.split(':')).reduce<Record<string, string>>((acc, [key, value]) => {
|
const serverSettingsQs = serverSettingsQsRaw.map(x => x.split(':')).reduce<Record<string, string>>((acc, [key, value]) => {
|
||||||
acc[key] = JSON.parse(value)
|
acc[key] = JSON.parse(value)
|
||||||
return acc
|
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 () {
|
function listenGlobalEvents () {
|
||||||
window.addEventListener('connect', e => {
|
window.addEventListener('connect', e => {
|
||||||
|
|
@ -133,7 +143,9 @@ function listenGlobalEvents () {
|
||||||
void connect(options)
|
void connect(options)
|
||||||
})
|
})
|
||||||
window.addEventListener('singleplayer', (e) => {
|
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
|
// 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)
|
localServer = window.localServer = window.server = startLocalServer(serverOptions)
|
||||||
|
connectOptions?.connectEvents?.serverCreated?.()
|
||||||
// todo need just to call quit if started
|
// todo need just to call quit if started
|
||||||
// loadingScreen.maybeRecoverable = false
|
// loadingScreen.maybeRecoverable = false
|
||||||
// init world, todo: do it for any async plugins
|
// init world, todo: do it for any async plugins
|
||||||
|
|
@ -644,13 +657,15 @@ export async function connect (connectOptions: ConnectOptions) {
|
||||||
setLoadingScreenStatus('Loading world')
|
setLoadingScreenStatus('Loading world')
|
||||||
})
|
})
|
||||||
|
|
||||||
const loadStart = Date.now()
|
|
||||||
let worldWasReady = false
|
let worldWasReady = false
|
||||||
const waitForChunksToLoad = async (progress?: ProgressReporter) => {
|
const waitForChunksToLoad = async (progress?: ProgressReporter) => {
|
||||||
await new Promise<void>(resolve => {
|
await new Promise<void>(resolve => {
|
||||||
|
if (worldWasReady) {
|
||||||
|
resolve()
|
||||||
|
return
|
||||||
|
}
|
||||||
const unsub = subscribe(appViewer.rendererState, () => {
|
const unsub = subscribe(appViewer.rendererState, () => {
|
||||||
if (worldWasReady) return
|
if (appViewer.rendererState.world.allChunksLoaded && appViewer.nonReactiveState.world.chunksTotalNumber) {
|
||||||
if (appViewer.rendererState.world.allChunksLoaded) {
|
|
||||||
worldWasReady = true
|
worldWasReady = true
|
||||||
resolve()
|
resolve()
|
||||||
unsub()
|
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 spawnEarlier = !singleplayer && !p2pMultiplayer
|
||||||
const displayWorld = async () => {
|
const displayWorld = async () => {
|
||||||
if (resourcePackState.isServerInstalling) {
|
if (resourcePackState.isServerInstalling) {
|
||||||
|
|
@ -679,11 +689,18 @@ export async function connect (connectOptions: ConnectOptions) {
|
||||||
})
|
})
|
||||||
await appViewer.resourcesManager.promiseAssetsReady
|
await appViewer.resourcesManager.promiseAssetsReady
|
||||||
}
|
}
|
||||||
console.log('try to focus window')
|
|
||||||
window.focus?.()
|
|
||||||
errorAbortController.abort()
|
errorAbortController.abort()
|
||||||
if (appStatusState.isError) return
|
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 {
|
try {
|
||||||
if (p2pConnectTimeout) clearTimeout(p2pConnectTimeout)
|
if (p2pConnectTimeout) clearTimeout(p2pConnectTimeout)
|
||||||
playerState.onlineMode = !!connectOptions.authenticatedAccount
|
playerState.onlineMode = !!connectOptions.authenticatedAccount
|
||||||
|
|
@ -932,7 +949,7 @@ if (!reconnectOptions) {
|
||||||
}
|
}
|
||||||
}, (err) => {
|
}, (err) => {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
alert(`Failed to download file: ${err}`)
|
alert(`Something went wrong: ${err}`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -945,3 +962,4 @@ if (initialLoader) {
|
||||||
window.pageLoaded = true
|
window.pageLoaded = true
|
||||||
|
|
||||||
void possiblyHandleStateVariable()
|
void possiblyHandleStateVariable()
|
||||||
|
registerOpenBenchmarkListener()
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import { isMajorVersionGreater } from './utils'
|
||||||
|
|
||||||
import { activeModalStacks, insertActiveModalStack, miscUiState } from './globalState'
|
import { activeModalStacks, insertActiveModalStack, miscUiState } from './globalState'
|
||||||
import supportedVersions from './supportedVersions.mjs'
|
import supportedVersions from './supportedVersions.mjs'
|
||||||
|
import { ConnectOptions } from './connect'
|
||||||
import { appQueryParams } from './appParams'
|
import { appQueryParams } from './appParams'
|
||||||
|
|
||||||
// todo include name of opened handle (zip)!
|
// 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> }
|
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
|
// todo test
|
||||||
if (miscUiState.gameLoaded) {
|
if (miscUiState.gameLoaded) {
|
||||||
await disconnect()
|
await disconnect()
|
||||||
|
|
@ -194,7 +195,8 @@ export const loadSave = async (root = '/world') => {
|
||||||
} : {},
|
} : {},
|
||||||
...root === '/world' ? {} : {
|
...root === '/world' ? {} : {
|
||||||
'worldFolder': root
|
'worldFolder': root
|
||||||
}
|
},
|
||||||
|
connectOptions
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,71 @@
|
||||||
|
import { subscribeKey } from 'valtio/utils'
|
||||||
import { preventThrottlingWithSound } from '../core/timers'
|
import { preventThrottlingWithSound } from '../core/timers'
|
||||||
import { options } from '../optionsStorage'
|
import { options } from '../optionsStorage'
|
||||||
|
|
||||||
customEvents.on('mineflayerBotCreated', () => {
|
customEvents.on('mineflayerBotCreated', () => {
|
||||||
if (options.preventBackgroundTimeoutKick) {
|
const abortController = new AbortController()
|
||||||
const unsub = preventThrottlingWithSound()
|
|
||||||
bot.on('end', unsub)
|
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 () {
|
custom () {
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ const defaultOptions = {
|
||||||
waitForChunksRender: 'sp-only' as 'sp-only' | boolean,
|
waitForChunksRender: 'sp-only' as 'sp-only' | boolean,
|
||||||
jeiEnabled: true as boolean | Array<'creative' | 'survival' | 'adventure' | 'spectator'>,
|
jeiEnabled: true as boolean | Array<'creative' | 'survival' | 'adventure' | 'spectator'>,
|
||||||
preventBackgroundTimeoutKick: false,
|
preventBackgroundTimeoutKick: false,
|
||||||
|
preventSleep: false,
|
||||||
|
|
||||||
// antiAliasing: false,
|
// antiAliasing: false,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,8 @@ export const defaultIndicatorsState = {
|
||||||
readonlyFiles: false,
|
readonlyFiles: false,
|
||||||
writingFiles: false, // saving
|
writingFiles: false, // saving
|
||||||
appHasErrors: false,
|
appHasErrors: false,
|
||||||
connectionIssues: 0
|
connectionIssues: 0,
|
||||||
|
preventSleep: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
const indicatorIcons: Record<keyof typeof defaultIndicatorsState, string> = {
|
const indicatorIcons: Record<keyof typeof defaultIndicatorsState, string> = {
|
||||||
|
|
@ -56,6 +57,7 @@ const indicatorIcons: Record<keyof typeof defaultIndicatorsState, string> = {
|
||||||
appHasErrors: 'alert',
|
appHasErrors: 'alert',
|
||||||
readonlyFiles: 'file-off',
|
readonlyFiles: 'file-off',
|
||||||
connectionIssues: pixelartIcons['cellular-signal-off'],
|
connectionIssues: pixelartIcons['cellular-signal-off'],
|
||||||
|
preventSleep: pixelartIcons.moon,
|
||||||
}
|
}
|
||||||
|
|
||||||
const colorOverrides = {
|
const colorOverrides = {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { proxy, subscribe, useSnapshot } from 'valtio'
|
import { proxy, subscribe, useSnapshot } from 'valtio'
|
||||||
import { useEffect, useMemo } from 'react'
|
import { useEffect, useMemo, useState } from 'react'
|
||||||
import { subscribeKey } from 'valtio/utils'
|
import { subscribeKey } from 'valtio/utils'
|
||||||
import { inGameError } from '../utils'
|
import { inGameError } from '../utils'
|
||||||
import { fsState } from '../loadSave'
|
import { fsState } from '../loadSave'
|
||||||
|
|
@ -51,6 +51,7 @@ const getEffectIndex = (newEffect: EffectType) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
|
const [dummyState, setDummyState] = useState(false)
|
||||||
const stateIndicators = useSnapshot(state.indicators)
|
const stateIndicators = useSnapshot(state.indicators)
|
||||||
const chunksLoading = !useSnapshot(appViewer.rendererState).world.allChunksLoaded
|
const chunksLoading = !useSnapshot(appViewer.rendererState).world.allChunksLoaded
|
||||||
const { mesherWork } = useSnapshot(appViewer.rendererState).world
|
const { mesherWork } = useSnapshot(appViewer.rendererState).world
|
||||||
|
|
@ -66,12 +67,21 @@ export default () => {
|
||||||
appHasErrors: hasErrors,
|
appHasErrors: hasErrors,
|
||||||
connectionIssues: poorConnection ? 1 : noConnection ? 2 : 0,
|
connectionIssues: poorConnection ? 1 : noConnection ? 2 : 0,
|
||||||
chunksLoading,
|
chunksLoading,
|
||||||
|
preventSleep: !!bot.wakeLock,
|
||||||
// mesherWork,
|
// mesherWork,
|
||||||
...stateIndicators,
|
...stateIndicators,
|
||||||
}
|
}
|
||||||
|
|
||||||
const effects = useSnapshot(state.effects)
|
const effects = useSnapshot(state.effects)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// update bot related states
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setDummyState(s => !s)
|
||||||
|
}, 1000)
|
||||||
|
return () => clearInterval(interval)
|
||||||
|
}, [])
|
||||||
|
|
||||||
useMemo(() => {
|
useMemo(() => {
|
||||||
const effectsImages = Object.fromEntries(loadedData.effectsArray.map((effect) => {
|
const effectsImages = Object.fromEntries(loadedData.effectsArray.map((effect) => {
|
||||||
const nameKebab = effect.name.replaceAll(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`).slice(1)
|
const nameKebab = effect.name.replaceAll(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`).slice(1)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue