Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance benchmark!! #153

Open
wants to merge 8 commits into
base: next
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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 [email protected]
- 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
# read benchmark results from stdout
- run: echo "BENCHMARK_RESULT=$(cat benchmark.txt)" >> $GITHUB_ENV
- uses: mshick/add-pr-comment@v2
with:
allow-repeats: true
message: |
Benchmark result: ${{ env.BENCHMARK_RESULT }}
4 changes: 3 additions & 1 deletion cypress.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { defineConfig } from 'cypress'

const isPerformanceTest = process.env.PERFORMANCE_TEST === 'true'

export default defineConfig({
video: false,
chromeWebSecurity: false,
Expand Down Expand Up @@ -31,7 +33,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/**/*.spec.ts' : 'cypress/e2e/rendering_performance.spec.ts',
excludeSpecPattern: ['**/__snapshots__/*', '**/__image_snapshots__/*'],
},
})
39 changes: 39 additions & 0 deletions cypress/e2e/rendering_performance.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/// <reference types="cypress" />
import { BenchmarkAdapter } from '../../src/benchmarkAdapter'
import { setOptions, cleanVisit, visit } from './shared'

it('Benchmark rendering performance', () => {
cleanVisit('/?openBenchmark=true&renderDistance=5')
// wait for render end event
return cy.document().then({ timeout: 120_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 BenchmarkAdapter
const renderTimeWorst = adapter.worstRenderTime
const renderTimeAvg = adapter.averageRenderTime
const fpsWorst = 1000 / renderTimeWorst
const fpsAvg = 1000 / renderTimeAvg
const totalTime = adapter.worldLoadTime
const { gpuInfo } = adapter

const messages = [
`Worst FPS: ${fpsWorst.toFixed(2)}`,
`Average FPS: ${fpsAvg.toFixed(2)}`,
`Total time: ${totalTime.toFixed(2)}s`,
`Memory usage average: ${adapter.memoryUsageAverage.toFixed(2)}MB`,
`Memory usage worst: ${adapter.memoryUsageWorst.toFixed(2)}MB`,
`GPU info: ${gpuInfo}`,
]
for (const message of messages) {
cy.log(message)
}
cy.writeFile('benchmark.txt', messages.join('\n'))
})
})
})
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"build": "node scripts/build.js copyFiles && node scripts/prepareData.mjs -f && node esbuild.mjs --minify --prod",
"check-build": "tsc && pnpm build",
"test:cypress": "cypress run",
"test:benchmark": "PERFORMANCE_TEST=true cypress run",
"test-unit": "vitest",
"test:e2e": "start-test http-get://localhost:8080 test:cypress",
"prod-start": "node server.js",
Expand Down
2 changes: 2 additions & 0 deletions prismarine-viewer/viewer/lib/viewerWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,11 @@ export class ViewerWrapper {
windowFocused = true
trackWindowFocus () {
window.addEventListener('focus', () => {
console.log('window focused')
this.windowFocused = true
})
window.addEventListener('blur', () => {
console.log('window blurred')
this.windowFocused = false
})
}
Expand Down
20 changes: 6 additions & 14 deletions prismarine-viewer/viewer/lib/worldrendererThree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export class WorldRendererThree extends WorldRendererCommon {
signsCache = new Map<string, any>()
starField: StarField
cameraSectionPos: Vec3 = new Vec3(0, 0, 0)
worstRenderTime = 0
avgRenderTime = 0

get tilesRendered () {
return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0)
Expand Down Expand Up @@ -72,20 +74,6 @@ export class WorldRendererThree extends WorldRendererCommon {
const chunkCoords = data.key.split(',')
if (!this.loadedChunks[chunkCoords[0] + ',' + chunkCoords[2]] || !data.geometry.positions.length || !this.active) return

// if (!this.initialChunksLoad && this.enableChunksLoadDelay) {
// const newPromise = new Promise(resolve => {
// if (this.droppedFpsPercentage > 0.5) {
// setTimeout(resolve, 1000 / 50 * this.droppedFpsPercentage)
// } else {
// setTimeout(resolve)
// }
// })
// this.promisesQueue.push(newPromise)
// for (const promise of this.promisesQueue) {
// await promise
// }
// }

const geometry = new THREE.BufferGeometry()
geometry.setAttribute('position', new THREE.BufferAttribute(data.geometry.positions, 3))
geometry.setAttribute('normal', new THREE.BufferAttribute(data.geometry.normals, 3))
Expand Down Expand Up @@ -163,7 +151,11 @@ export class WorldRendererThree extends WorldRendererCommon {
render () {
tweenJs.update()
const cam = this.camera instanceof THREE.Group ? this.camera.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera
const start = performance.now()
this.renderer.render(this.scene, cam)
const totalTime = performance.now() - start
this.avgRenderTime = this.avgRenderTime * 0.9 + totalTime * 0.1 // exponential moving average
this.worstRenderTime = Math.max(this.worstRenderTime, totalTime)
}

renderSign (position: Vec3, rotation: number, isWall: boolean, isHanging: boolean, blockEntity) {
Expand Down
77 changes: 77 additions & 0 deletions src/benchmark.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Vec3 } from 'vec3'
import { downloadAndOpenFileFromUrl } from './downloadAndOpenFile'
import { activeModalStack, miscUiState } from './globalState'
import { options } from './optionsStorage'
import { BenchmarkAdapter } from './benchmarkAdapter'

const testWorldFixtureUrl = 'https://bucket.mcraft.fun/Future CITY 4.4-slim.zip'
const testWorldFixtureSpawn = [-133, 87, 309] as const

export const openBenchmark = async (renderDistance = 8) => {
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)

const benchmarkAdapter: BenchmarkAdapter = {
get worldLoadTime () {
return window.worldLoadTime
},
get averageRenderTime () {
return window.viewer.world.avgRenderTime
},
get worstRenderTime () {
return window.viewer.world.worstRenderTime
},
get memoryUsageAverage () {
return memoryUsageAverage
},
get memoryUsageWorst () {
return memoryUsageWorst
},
get gpuInfo () {
const gl = window.viewer.renderer.getContext()
return gl.getParameter(gl.getExtension('WEBGL_debug_renderer_info')!.UNMASKED_RENDERER_WEBGL)
}
}
window.benchmarkAdapter = benchmarkAdapter

options.renderDistance = renderDistance
void downloadAndOpenFileFromUrl(testWorldFixtureUrl, undefined, {
connectEvents: {
serverCreated () {
if (testWorldFixtureSpawn) {
localServer!.spawnPoint = new Vec3(...testWorldFixtureSpawn)
localServer!.on('newPlayer', (player) => {
player.on('dataLoaded', () => {
player.position = new Vec3(...testWorldFixtureSpawn)
})
})
}
},
}
})
}

export const registerOpenBenchmarkListener = () => {
const params = new URLSearchParams(window.location.search)
if (params.get('openBenchmark')) {
void openBenchmark(params.has('renderDistance') ? +params.get('renderDistance')! : undefined)
}

window.addEventListener('keydown', (e) => {
if (e.code === 'KeyB' && e.shiftKey && !miscUiState.gameLoaded && activeModalStack.length === 0) {
e.preventDefault()
void openBenchmark()
}
})
}
8 changes: 8 additions & 0 deletions src/benchmarkAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export interface BenchmarkAdapter {
worldLoadTime: number
averageRenderTime: number
worstRenderTime: number
memoryUsageAverage: number
memoryUsageWorst: number
gpuInfo: string
}
5 changes: 3 additions & 2 deletions src/browserfs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { fsState, loadSave } from './loadSave'
import { installTexturePack, installTexturePackFromHandle, updateTexturePackInstalledState } from './texturePack'
import { miscUiState } from './globalState'
import { setLoadingScreenStatus } from './utils'
import { ConnectOptions } from './connect'
const { GoogleDriveFileSystem } = require('google-drive-browserfs/src/backends/GoogleDrive') // disable type checking

browserfs.install(window)
Expand Down Expand Up @@ -434,7 +435,7 @@ export const copyFilesAsync = async (pathSrc: string, pathDest: string, fileCopi
}

// 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
Expand Down Expand Up @@ -478,7 +479,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
}

Expand Down
12 changes: 10 additions & 2 deletions src/connect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,22 @@ export type ConnectOptions = {
singleplayer?: any
username: string
proxy?: string
botVersion?: any
botVersion?: string
serverOverrides?
serverOverridesFlat?
peerId?: string
ignoreQs?: boolean
onSuccessfulPlay?: () => void
autoLoginPassword?: string
serverIndex?: string
/** If true, will show a UI to authenticate with a new account */
authenticatedAccount?: AuthenticatedAccount | true

connectEvents?: {
serverCreated?: () => void
// connect: () => void;
// disconnect: () => void;
// error: (err: any) => void;
// ready: () => void;
// end: () => void;
}
}
21 changes: 11 additions & 10 deletions src/downloadAndOpenFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,25 @@
import { openWorldZip } from './browserfs'
import { getResourcePackName, installTexturePack, resourcePackState, updateTexturePackInstalledState } from './texturePack'
import { setLoadingScreenStatus } from './utils'
import { ConnectOptions } from './connect'

export const getFixedFilesize = (bytes: number) => {
return prettyBytes(bytes, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
}

const inner = async () => {
const qs = new URLSearchParams(window.location.search)
let mapUrl = qs.get('map')
const texturepack = qs.get('texturepack')
export const downloadAndOpenFileFromUrl = 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 getResourcePackName()} 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)
Expand All @@ -43,7 +41,7 @@

// eslint-disable-next-line no-constant-condition
while (true) {
const { done, value } = await reader.read()

Check warning on line 44 in src/downloadAndOpenFile.ts

View workflow job for this annotation

GitHub Actions / build-and-deploy

Unexpected `await` inside a loop

if (done) {
controller.close()
Expand All @@ -63,17 +61,20 @@
},
})
).arrayBuffer()
if (texturepack) {
if (texturepackUrl) {
const name = mapUrl.slice(mapUrl.lastIndexOf('/') + 1).slice(-30)
await installTexturePack(buffer, name)
} else {
await openWorldZip(buffer)
await openWorldZip(buffer, undefined, connectOptions)
}
}

export default async () => {
try {
return await inner()
const qs = new URLSearchParams(window.location.search)
const mapUrl = qs.get('map')
const texturepack = qs.get('texturepack')
return await downloadAndOpenFileFromUrl(mapUrl ?? undefined, texturepack ?? undefined)
} catch (err) {
setLoadingScreenStatus(`Failed to download. Either refresh page or remove map param from URL. Reason: ${err.message}`)
return true
Expand Down
Loading
Loading